├── .gitignore ├── README.rst ├── data └── test │ ├── kdata.csv │ └── signal.csv ├── dependency.py ├── figure_money.png ├── figure_signal.png ├── install.py ├── install_dependency.py ├── install_pip.py ├── plot.png ├── pyquant.png ├── quantdigger ├── __init__.py ├── backup │ ├── backtest │ │ ├── config.py │ │ ├── datasource │ │ │ ├── sina.py │ │ │ ├── sina_format │ │ │ └── yahoo_format │ │ ├── loader.py │ │ ├── main.py │ │ ├── mainwindow.py │ │ ├── mainwindow_ui.py │ │ ├── mainwindow_ui.ui │ │ ├── matplotlibwidget.py │ │ ├── resource.qrc │ │ ├── resource_rc.py │ │ ├── strategy │ │ │ └── demo.py │ │ ├── strategy_runner.py │ │ └── utils.py │ ├── demo │ │ ├── 3d.py │ │ ├── entry_3d.py │ │ ├── k.py │ │ ├── pick.py │ │ ├── selctor.py │ │ ├── subplot.py │ │ ├── summary.py │ │ ├── tech.py │ │ └── temp.py │ ├── pyqt_kline │ │ ├── CandleWidget.py │ │ ├── QuantModel.py │ │ ├── QuantTestData.py │ │ ├── QuantUtils.py │ │ ├── QuantView.py │ │ ├── QuantView.py_bk │ │ ├── QuoteWidget.py │ │ ├── QxtSpanSlider.py │ │ ├── TrendsWidget.py │ │ ├── __init__.py │ │ ├── _no_data.png │ │ ├── main.py │ │ └── readme │ ├── techmplot2.py │ └── zipline 2014-1-17 │ │ ├── zipline2.pdf │ │ └── zipline3.pdf ├── demo │ ├── .DS_Store │ ├── IF000.SHFE-10.Minute.csv │ ├── __init__.py │ ├── main.py │ ├── mplot_demo.py │ ├── plot_ma.py │ ├── plotting.py │ └── pyquant.py ├── digger │ ├── __init__.py │ ├── analyze.py │ ├── deals.py │ └── finance.py ├── errors.py ├── images │ ├── new.png │ ├── print.png │ ├── save.png │ └── undo.png ├── kernel │ ├── __init__.py │ ├── datasource │ │ ├── __init__.py │ │ ├── data.py │ │ ├── data │ │ │ ├── stock_data │ │ │ │ └── _IF000.csv │ │ │ └── trace │ │ │ │ ├── _djtrend2_IF000.csv │ │ │ │ └── _djtrend2_IF000_.csv │ │ ├── data_manager.py │ │ └── quote_cache.py │ ├── datastruct.py │ ├── engine │ │ ├── SHFE-IF000-Minutes-10.csv │ │ ├── __init__.py │ │ ├── api.py │ │ ├── blotter.py │ │ ├── data.py │ │ ├── event.py │ │ ├── exchange.py │ │ ├── execute_unit.py │ │ ├── series.py │ │ ├── strategy.py │ │ ├── todo │ │ └── trader.py │ └── indicators │ │ ├── __init__.py │ │ ├── common.py │ │ └── indicator.py ├── util.py └── widgets │ ├── __init__.py │ ├── mplotwidgets │ ├── __init__.py │ ├── mplots.py │ ├── stock_plot.py │ └── widgets.py │ ├── plotting.py │ ├── plugin_interface.py │ └── qtwidgets │ ├── __init__.py │ └── techwidget.py ├── requirements.txt ├── setup.py ├── update.rst └── util.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | QuantDigger 2 | ============ 3 | 4 | QuantDigger目前是一个基于python的量化回测框架。作者最初是因为对数据处理和机器学习感兴趣而选择了这个行业, 5 | 接触了一些主流的期货交易软件,比如TB, 金字塔。他们的特点是语法比较简单,缺点是编程语言太封闭,有很多表达限制。 6 | 所以选择自己开发一个交易系统,做为交易和研究的工具,甚至尝试过商业化。最初选择c++做为实现语言,但是后面 7 | 发现开发效率太低,重要的是做为研究工具来说,其易用性和和扩展性都比不上基于python的回测框架。相比其它流行的 8 | 回测框架比如 zipline_ , pyalgotrade_ ,QuantDigger的策略语法更简单,类似MC,TB这些商业软件,但并不牺牲灵活性,保留了python这门通用语言的 9 | 所有功能。QuantDigger目前还是定位于研究工具,但是设计上还是会从实盘交易的角度考虑,将来也会接入交易接口。虽然有很多细节还有待完善, 10 | 但是核心的设计和功能已经实现了。代码也比较简单,大家有兴趣的可以自己拓展。 如果大家有什么问题和建议,欢迎加入我们的QQ交流群--334555399,或者 11 | 联系发起者(yellowblue QQ:33830957) 。 在项目的推进过程中得到很多朋友的帮助, 在这表示感谢! 12 | 除了开发人员,还要特别感谢北京的 vodkabuaa_ 和国元证券的王林峰给出的意见, ongbe_ 帮忙修复代码bug, tushare_ 库的作者 Jimmy_ 和深大的邓志浩帮忙推荐 13 | 这个库,以及所有朋友的支持。 14 | 15 | **主要代码贡献者:** 16 | deepfish_ 17 | 18 | TeaEra_ 19 | 20 | wondereamer_ 21 | 22 | HonePhy_ 23 | 24 | 文档 25 | ---- 26 | http://www.quantfans.com/doc/quantdigger/ 27 | 28 | 29 | 安装 30 | ---- 31 | 32 | 你可以选择pip安装 (推荐) 33 | 34 | :: 35 | 36 | python install_pip.py (如果已经安装了pip,略过这一步。) 37 | pip install QuantDigger 38 | python install_dependency.py 39 | 40 | 或者克隆github代码后本地安装 41 | 42 | :: 43 | 44 | git clone https://github.com/QuantFans/quantdigger.git 45 | python install.py (会根据情况安装pip, 及依赖包) 46 | 47 | 48 | 依赖库 49 | ------ 50 | * Python 51 | * pandas 52 | * python-dateutil 53 | * matplotlib 54 | * numpy 55 | * TA-Lib 56 | * logbook 57 | * pyqt (可选) 58 | * tushare_ (可选, 一个非常强大的股票信息抓取工具) 59 | 60 | 策略DEMO 61 | -------- 62 | 源码 63 | ~~~~ 64 | .. code:: py 65 | 66 | from quantdigger.kernel.engine.execute_unit import ExecuteUnit 67 | from quantdigger.kernel.indicators.common import MA, BOLL 68 | from quantdigger.kernel.engine.strategy import TradingStrategy 69 | from quantdigger.util import pcontract 70 | import plotting 71 | 72 | class DemoStrategy(TradingStrategy): 73 | """ 策略实例 """ 74 | def __init__(self, exe): 75 | super(DemoStrategy, self).__init__(exe) 76 | # 创建平均线指标和布林带指标。其中MA和BOLL表示指标函数类。 77 | # 它们返回序列变量。 78 | # 'ma20':指标名. 'b'画线颜色. ‘1‘: 线宽。如果无需 79 | # 绘图,则这些参数不需要给出。 80 | self.ma20 = MA(self, self.close, 20,'ma20', 'b', '1') 81 | self.ma10 = MA(self, self.close, 10,'ma10', 'y', '1') 82 | self.b_upper, self.b_middler, self.b_lower = BOLL(self, self.close, 10,'boll10', 'y', '1') 83 | 84 | def on_bar(self): 85 | """ 策略函数,对每根Bar运行一次。""" 86 | if self.ma10[1] < self.ma20[1] and self.ma10 > self.ma20: 87 | self.buy('long', self.open, 1, contract = 'IF000.SHFE') 88 | elif self.position() > 0 and self.ma10[1] > self.ma20[1] and self.ma10 < self.ma20: 89 | self.sell('long', self.open, 1) 90 | 91 | # 输出pcon1的当前K线开盘价格。 92 | print(self.open) 93 | 94 | # 夸品种数据引用 95 | # pcon2的前一根K线开盘价格。 96 | print(self.open_(1)[1]) 97 | 98 | if __name__ == '__main__': 99 | try: 100 | # 策略的运行对象周期合约 101 | pcon1 = pcontract('IF000.SHFE', '10.Minute') 102 | pcon2 = pcontract('IF000.SHFE', '10.Minute') 103 | # 创建模拟器,这里假设策略要用到两个不同的数据,比如套利。 104 | simulator = ExecuteUnit([pcon1, pcon2]); 105 | # 创建策略。 106 | algo = DemoStrategy(simulator) 107 | # 运行模拟器,这里会开始事件循环。 108 | simulator.run() 109 | 110 | # 显示回测结果 111 | plotting.plot_result(simulator.data[pcon], algo._indicators, 112 | algo.blotter.deal_positions, algo.blotter) 113 | 114 | except Exception, e: 115 | print(e) 116 | 117 | 118 | 策略结果 119 | ~~~~~~~~ 120 | **main.py** 121 | 122 | * k线和信号线 123 | 124 | .. image:: figure_signal.png 125 | :width: 500px 126 | 127 | * 资金曲线。 128 | 129 | .. image:: figure_money.png 130 | :width: 500px 131 | 132 | 其它 133 | ~~~~~~~~ 134 | **mplot_demo.py matplotlib画k线,指标线的demo。** 135 | .. image:: plot.png 136 | :width: 500px 137 | 138 | **pyquant.py 基于pyqt, 集成了ipython和matplotlib的demo。** 139 | .. image:: pyquant.png 140 | :width: 500px 141 | 142 | .. _TeaEra: https://github.com/TeaEra 143 | .. _deepfish: https://github.com/deepfish 144 | .. _wondereamer: https://github.com/wondereamer 145 | .. _HonePhy: https://github.com/HonePhy 146 | .. _tushare: https://github.com/waditu/tushare 147 | .. _Jimmy: https://github.com/jimmysoa 148 | .. _vodkabuaa: https://github.com/vodkabuaa 149 | .. _ongbe: https://github.com/ongbe 150 | .. _pyalgotrade: https://github.com/gbeced/pyalgotrade 151 | .. _zipline: https://github.com/quantopian/zipline 152 | 153 | 154 | 版本 155 | ~~~~ 156 | 157 | **TODO** 158 | 159 | * 清理旧代码和数据文件 160 | * 重新设计数据模块 161 | * 改善UI, 补充UI文档 162 | 163 | **0.2.0 版本 2015-08-18** 164 | 165 | * 修复股票回测的破产bug 166 | * 修复回测权益计算bug 167 | * 交易信号对的计算从回测代码中分离 168 | * 把回测金融指标移到digger/finace 169 | * 添加部分数据结构,添加部分数据结构字段 170 | * 添加几个mongodb相关的函数 171 | 172 | **0.15版本 2015-06-16** 173 | 174 | * 夸品种的策略回测功能 175 | * 简单的交互 176 | * 指标,k线绘制 177 | -------------------------------------------------------------------------------- /data/test/kdata.csv: -------------------------------------------------------------------------------- 1 | datetime,open,close,high,low,vol 2 | 2010-04-16 09:15:00,3497.200000,3475.200000,3517.400000,3465.300000,5290 3 | 2010-04-16 09:30:00,3474.600000,3472,3483.600000,3468.600000,4574 4 | 2010-04-16 09:45:00,3472.200000,3472.100000,3472.700000,3465,4218 5 | 2010-04-16 10:00:00,3472.100000,3465.300000,3472.400000,3462.900000,3152 6 | 2010-04-16 10:15:00,3465.300000,3454,3466.600000,3451.900000,4175 7 | 2010-04-16 10:30:00,3453.700000,3455.300000,3457.400000,3450.900000,3151 8 | 2010-04-16 10:45:00,3455.300000,3454.700000,3455.800000,3453.100000,2099 9 | 2010-04-16 11:00:00,3454.700000,3455.200000,3455.400000,3452.700000,1708 10 | 2010-04-16 11:15:00,3455.200000,3454.300000,3457.100000,3453.700000,1953 11 | 2010-04-16 13:00:00,3454.300000,3451.900000,3454.600000,3450.900000,1413 12 | 2010-04-16 13:15:00,3452,3450.200000,3452,3449.300000,1712 13 | 2010-04-16 13:30:00,3450.400000,3457.600000,3458.600000,3449.500000,3312 14 | 2010-04-16 13:45:00,3458.300000,3465.900000,3466,3455.300000,3395 15 | 2010-04-16 14:00:00,3465.300000,3462.700000,3467.100000,3461.600000,4171 16 | -------------------------------------------------------------------------------- /data/test/signal.csv: -------------------------------------------------------------------------------- 1 | entry_datetime,entry_price,exit_datetime,exit_price 2 | 2010-05-24 11:00:00,2932,2010-05-25 13:45:00,2836 3 | 2010-07-09 14:00:00,2668,2010-07-15 09:45:00,2646 4 | 2010-07-19 14:45:00,2710,2010-08-05 14:45:00,2842 5 | 2010-08-16 14:45:00,2950,2010-08-20 10:45:00,2950 6 | 2010-09-06 13:45:00,2988,2010-09-09 13:00:00,2954 7 | 2010-10-08 09:15:00,2982,2010-11-12 13:30:00,3412 8 | 2010-12-13 13:30:00,3252,2010-12-20 11:00:00,3256 9 | 2011-01-31 11:15:00,3092,2011-02-09 09:30:00,3076 10 | 2011-02-14 10:45:00,3198,2011-02-22 11:15:00,3192 11 | 2011-03-07 09:30:00,3318,2011-03-10 09:45:00,3334 12 | -------------------------------------------------------------------------------- /dependency.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | import util 4 | def install_talib_for_windows(): 5 | url = 'http://downloads.sourceforge.net/project/ta-lib/ta-lib/0.4.0/ta-lib-0.4.0-msvc.zip' 6 | target = 'ta-lib-0.4.0-msvc.zip' 7 | util.download(url,target) 8 | util.decompressZip(target,'C:\\') 9 | 10 | def install_talib_for_linux(): 11 | url = 'http://downloads.sourceforge.net/project/ta-lib/ta-lib/0.4.0/ta-lib-0.4.0-src.tar.gz' 12 | target = 'ta-lib-0.4.0-src.tar.gz' 13 | util.download(url,target) 14 | util.decompress(target,'.') 15 | os.chdir('ta-lib') 16 | print('==========configure ta-lib============') 17 | result = os.popen('./configure').readlines() 18 | util.printCommandResult(result) 19 | print('==========configure end ============') 20 | print('==========make ta-lib ================') 21 | result = os.popen('make').readlines() 22 | util.printCommandResult(result) 23 | print('==========make ta-lib end ============') 24 | print('==========make install tab-lib =======') 25 | result = os.popen('make install').readlines() 26 | util.printCommandResult(result) 27 | print('==========make install tab-lib end =======') 28 | 29 | def install_talib_for_Darwin(): 30 | result = os.popen('brew install ta-lib').readlines() 31 | util.printCommandResult(result) 32 | 33 | def pip_download_install(): 34 | url = 'https://pypi.python.org/packages/source/p/pip/pip-6.0.8.tar.gz' 35 | target = 'pip-6.0.8.tar.gz' 36 | targetdir = 'pip-6.0.8' 37 | print('============ downloading ' + target + ' from:' + url) 38 | util.download(url,target) 39 | print('============ extracting ' + target) 40 | util.decompress(target,'.') 41 | os.chdir(targetdir) 42 | print('============ installing pip') 43 | cmdResult = os.popen('python setup.py install').readlines() 44 | util.printCommandResult(cmdResult) 45 | print('============ installed,plese add pip to your path') 46 | 47 | def create_dependencies(): 48 | requirements = '' 49 | libs = [ ('numpy', 'numpy'), 50 | ('pandas', 'pandas'), 51 | ('dateutil', 'python-dateutil'), 52 | ('matplotlib', 'matplotlib'), 53 | ('logbook', 'logbook'), 54 | ('talib' , 'TA-Lib == 0.4.8') 55 | ] 56 | for lib in libs: 57 | try: 58 | __import__(lib[0]) 59 | print lib[0], "already installed!" 60 | except ImportError: 61 | requirements += '%s\n' % lib[1] 62 | if requirements: 63 | file = open('requirements.txt','w') 64 | file.write(requirements) 65 | file.close() 66 | return requirements 67 | 68 | def handle_dependency(): 69 | """docstring for fn""" 70 | platform_name = platform.system() 71 | try: 72 | if platform_name == 'Windows': 73 | install_talib_for_windows() 74 | elif platform_name == 'Linux': 75 | install_talib_for_linux() 76 | elif platform_name == 'Darwin': 77 | install_talib_for_Darwin() 78 | else: 79 | print('Failed to install ta-lib!') 80 | except Exception, e: 81 | print('Failed to install ta-lib!') 82 | print(e) 83 | 84 | dependencies = create_dependencies() 85 | if dependencies: 86 | print('pip install -r requirements.txt') 87 | print(dependencies) 88 | result = os.popen('pip install -r requirements.txt').readlines() 89 | util.printCommandResult(result) 90 | -------------------------------------------------------------------------------- /figure_money.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/figure_money.png -------------------------------------------------------------------------------- /figure_signal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/figure_signal.png -------------------------------------------------------------------------------- /install.py: -------------------------------------------------------------------------------- 1 | import dependency 2 | import util 3 | import os 4 | import re 5 | 6 | # install quantdigger 7 | print("----------- install QuantDigger --------------" ) 8 | result = os.popen('python setup.py install').readlines() 9 | util.printCommandResult(result) 10 | 11 | 12 | print("----------- install pip --------------" ) 13 | result = os.popen('pip -V').read() 14 | reobj = re.compile("pip.+from.+", re.IGNORECASE) 15 | if reobj.search(result): 16 | print('pip has be installed') 17 | else: 18 | print('pip no install') 19 | dependency.pip_download_install() 20 | 21 | print("----------- install dpendencies --------------" ) 22 | dependency.handle_dependency() 23 | 24 | -------------------------------------------------------------------------------- /install_dependency.py: -------------------------------------------------------------------------------- 1 | import dependency 2 | 3 | 4 | dependency.handle_dependency() 5 | -------------------------------------------------------------------------------- /install_pip.py: -------------------------------------------------------------------------------- 1 | import dependency 2 | 3 | dependency.pip_download_install() 4 | -------------------------------------------------------------------------------- /plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/plot.png -------------------------------------------------------------------------------- /pyquant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/pyquant.png -------------------------------------------------------------------------------- /quantdigger/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/quantdigger/__init__.py -------------------------------------------------------------------------------- /quantdigger/backup/backtest/config.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Wenwei Huang' 2 | 3 | data_path='./data' 4 | cache_path = './cache' 5 | strategy_path='./strategy' 6 | 7 | main_curve_color = '#3399FF' 8 | 9 | -------------------------------------------------------------------------------- /quantdigger/backup/backtest/datasource/sina.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python 2 | #coding=utf-8 3 | 4 | import os, io, sys, re, time, json, base64 5 | import webbrowser, urllib 6 | 7 | #国内股票数据:指数 8 | def get_sina_index_daily(stockCode): 9 | try: 10 | exchange = "sz" if (int(stockCode) // 100000 == 3) else "sh" 11 | #http://hq.sinajs.cn/list=s_sh000001 12 | dataUrl = "http://hq.sinajs.cn/list=s_" + exchange + stockCode 13 | stdout = urllib.urlopen(dataUrl) 14 | stdoutInfo = stdout.read().decode('gb2312') 15 | tempData = re.search('''(")(.+)(")''', stdoutInfo).group(2) 16 | stockInfo = tempData.split(",") 17 | #stockCode = stockCode, 18 | stockName = stockInfo[0] 19 | stockEnd = stockInfo[1] #当前价,15点后为收盘价 20 | stockZD = stockInfo[2] #涨跌 21 | lastPrice = str(float(stockEnd) - float(stockZD)) #开盘价 22 | stockFD = stockInfo[3] #幅度 23 | stockZS = stockInfo[4] #总手 24 | stockZS_W = str(int(stockZS) / 100) 25 | stockJE = stockInfo[5] #金额 26 | stockJE_Y = str(int(stockJE) / 10000) 27 | content = "#" + stockName + "#" + "(" + str(stockCode) + ")" + u" 收盘:" \ 28 | + stockEnd + u",涨跌:" + stockZD + u",幅度:" + stockFD + "%" \ 29 | + u",总手:" + stockZS_W + u"万" + u",金额:" + stockJE_Y + u"亿" + " " 30 | 31 | #imgPath = "http://image.sinajs.cn/newchart/" + period + "/n/" + exchange + str(stockCode) + ".gif" 32 | 33 | 34 | except Exception as e: 35 | print(">>>>>> Exception: " + str(e)) 36 | else: 37 | return content 38 | finally: 39 | None 40 | 41 | #国内股票数据:个股 42 | def get_sina_stock_daily(stockCode): 43 | try: 44 | exchange = "sh" if (int(stockCode) // 100000 == 6) else "sz" 45 | dataUrl = "http://hq.sinajs.cn/list=" + exchange + stockCode 46 | stdout = urllib.urlopen(dataUrl) 47 | stdoutInfo = stdout.read().decode('gb2312') 48 | tempData = re.search('''(")(.+)(")''', stdoutInfo).group(2) 49 | stockInfo = tempData.split(",") 50 | #stockCode = stockCode, 51 | stockName = stockInfo[0] #名称 52 | stockStart = stockInfo[1] #开盘 53 | stockLastEnd= stockInfo[2] #昨收盘 54 | stockCur = stockInfo[3] #当前 55 | stockMax = stockInfo[4] #最高 56 | stockMin = stockInfo[5] #最低 57 | stockUp = round(float(stockCur) - float(stockLastEnd), 2) 58 | stockRange = round(float(stockUp) / float(stockLastEnd), 4) * 100 59 | stockVolume = round(float(stockInfo[8]) / (100 * 10000), 2) 60 | stockMoney = round(float(stockInfo[9]) / (100000000), 2) 61 | stockTime = stockInfo[31] 62 | 63 | content = "#" + stockName + "#(" + stockCode + ")" + u" 开盘:" + stockStart \ 64 | + u",最新:" + stockCur + u",最高:" + stockMax + u",最低:" + stockMin \ 65 | + u",涨跌:" + str(stockUp) + u",幅度:" + str(stockRange) + "%" \ 66 | + u",总手:" + str(stockVolume) + u"万" + u",金额:" + str(stockMoney) \ 67 | + u"亿" + u",更新时间:" + stockTime + " " 68 | 69 | #imgUrl = "http://image.sinajs.cn/newchart/" + period + "/n/" + exchange + str(stockCode) + ".gif" 70 | 71 | 72 | except Exception as e: 73 | print(">>>>>> Exception: " + str(e)) 74 | else: 75 | return content 76 | finally: 77 | None 78 | 79 | #全球股票指数 80 | def get_yahoo_daily(stockDict): 81 | try: 82 | #http://download.finance.yahoo.com/d/quotes.csv?s=^IXIC&f=sl1c1p2l 83 | yahooCode = stockDict['yahoo'] 84 | dataUrl = "http://download.finance.yahoo.com/d/quotes.csv?s=" + yahooCode + "&f=sl1c1p2l" 85 | 86 | stdout = urllib.urlopen(dataUrl) 87 | stdoutInfo = stdout.read().decode('gb2312') 88 | tempData = stdoutInfo.replace('"', '') 89 | stockInfo = tempData.split(",") 90 | #stockNameCn = stockDict['name']['chinese'] 91 | #stockNameEn = stockDict['name']['english'] 92 | stockCode = stockDict['code'] 93 | stockEnd = stockInfo[1] #当前价,5点后为收盘价 94 | stockZD = stockInfo[2] #涨跌 95 | lastPrice = str(float(stockEnd) - float(stockZD)) #开盘价 96 | stockFD = stockInfo[3] #幅度 97 | #percent = float(stockFD.replace("%", "")) 98 | matchResult = re.search("([\w?\s?:]*)(\-)", stockInfo[4]) #日期和最新值 99 | stockDate = matchResult.group(1) 100 | 101 | content = "(" + stockCode + ")" \ 102 | + u" 当前:" + stockEnd + u", 涨跌:" + stockZD + u", 幅度:" + stockFD \ 103 | + u", 最后交易时间:" + stockDate 104 | 105 | except Exception as err: 106 | print(">>>>>> Exception: " + yahooCode ) 107 | print "error: " + str(err) 108 | else: 109 | return content 110 | finally: 111 | None 112 | #-------------------------------------------------------------------------------------------------------- 113 | chinaIndex = [ 114 | "000001", # sh000001 上证指数 115 | "399001", # sz399001 深证成指 116 | "000300", # sh000300 沪深300 117 | "399005", # sz399005 中小板指 118 | "399006", # sz399006 创业板指 119 | "000003", # sh000003 B股指数 120 | ] 121 | worldIndex = [ 122 | {'code':"000001", 'yahoo':"000001.SS",'name':{'chinese':u"中国上证指数", 'english':"CHINA SHANGHAI COMPOSITE INDEX"}}, 123 | {'code':"399001", 'yahoo':"399001.SZ",'name':{'chinese':u"中国深证成指", 'english':"SZSE COMPONENT INDEX"}}, 124 | {'code':"DJI", 'yahoo':"^DJI",'name':{'chinese':u"美国道琼斯工业平均指数", 'english':"Dow Jones Industrial Average"}}, 125 | {'code':"IXIC", 'yahoo':"^IXIC",'name':{'chinese':u"美国纳斯达克综合指数", 'english':"NASDAQ Composite"},}, 126 | {'code':"GSPC", 'yahoo':"^GSPC",'name':{'chinese':u"美国标准普尔500指数", 'english':"S&P 500"}}, 127 | {'code':"N225", 'yahoo':"^N225",'name':{'chinese':u"日本日经225指数", 'english':"NIKKEI 225"}}, 128 | {'code':"TWII", 'yahoo':"^TWII",'name':{'chinese':u"台湾台北加权指数", 'english':"TSEC weighted index"}}, 129 | {'code':"HSI", 'yahoo':"^HSI",'name':{'chinese':u"香港恒生指数", 'english':"HANG SENG INDEX"}}, 130 | {'code':"FCHI", 'yahoo':"^FCHI",'name':{'chinese':u"法国CAC40指数", 'english':"CAC 40"}}, 131 | {'code':"FTSE", 'yahoo':"^FTSE",'name':{'chinese':u"英国富时100指数", 'english':"FTSE 100"}}, 132 | {'code':"GDAXI", 'yahoo':"^GDAXI",'name':{'chinese':u"德国法兰克福DAX指数", 'english':"DAX"} } 133 | ] 134 | chinaStock = [ 135 | "000063", # 中兴通讯 136 | "600036", # 招商银行 137 | ] 138 | 139 | def main(): 140 | "main function" 141 | print "test world stock index:" 142 | for stockDict in worldIndex: 143 | print get_yahoo_daily(stockDict) 144 | 145 | 146 | if __name__ == '__main__': 147 | import pycallgraph 148 | pycallgraph.start_trace() 149 | main() 150 | pycallgraph.make_dot_graph('r.png') 151 | -------------------------------------------------------------------------------- /quantdigger/backup/backtest/datasource/sina_format: -------------------------------------------------------------------------------- 1 | 0:”大秦铁路”,股票名字; 2 | 1:”27.55″,今日开盘价; 3 | 2:”27.25″,昨日收盘价; 4 | 3:”26.91″,当前价格; 5 | 4:”27.55″,今日最高价; 6 | 5:”26.20″,今日最低价; 7 | 6:”26.91″,竞买价,即“买一”报价; 8 | 7:”26.92″,竞卖价,即“卖一”报价; 9 | 8:”22114263″,成交的股票数,由于股票交易以一百股为基本单位,所以在使用时,通常把该值除以一百; 10 | 9:”589824680″,成交金额,单位为“元”,为了一目了然,通常以“万元”为成交金额的单位,所以通常把该值除以一万; 11 | 10:”4695″,“买一”申请4695股,即47手; 12 | 11:”26.91″,“买一”报价; 13 | 12:”57590″,“买二” 14 | 13:”26.90″,“买二” 15 | 14:”14700″,“买三” 16 | 15:”26.89″,“买三” 17 | 16:”14300″,“买四” 18 | 17:”26.88″,“买四” 19 | 18:”15100″,“买五” 20 | 19:”26.87″,“买五” 21 | 20:”3100″,“卖一”申报3100股,即31手; 22 | 21:”26.92″,“卖一”报价 23 | (22, 23), (24, 25), (26,27), (28, 29)分别为“卖二”至“卖四的情况” 24 | 30:”2008-01-11″,日期; 25 | 31:”15:05:32″,时间; 26 | -------------------------------------------------------------------------------- /quantdigger/backup/backtest/datasource/yahoo_format: -------------------------------------------------------------------------------- 1 | http://table.finance.yahoo.com/table.csv?s=ibm&d=6&e=22&f=2006&g=d&a=11&b=16&c=1991&ignore=.csv 2 | 上面的链接可以抓取IBM股票从1991年11月16日起到2006年6月22的数据。把ibm改成sohu,就可以抓到sohu的股票数据了。 3 | http://table.finance.yahoo.com/table.csv?s=sohu&d=6&e=22&f=2008&g=d&a=11&b=16&c=2008&ignore=.csv 4 | 上面链接可以抓搜狐股票的数据。 5 | 6 |   那么中国股市的数据有没有呢?答案是肯定的,不过要按照下面的参数做些调整,下面提供全球证券交易所的资料。 7 | 上证股票是股票代码后面加上.ss,深证股票是股票代码后面加上.sz 8 | 例如:000001 = 000001.sz 9 | 深市数据链接:http://table.finance.yahoo.com/table.csv?s=000001.sz 10 | 上市数据链接:http://table.finance.yahoo.com/table.csv?s=600000.ss 11 | 上证综指代码:000001.ss,深证成指代码:399001.SZ,沪深300代码:000300.ss 12 | 13 | 下面就是世界股票交易所的网址和缩写,要查找哪个股票交易所的数据,就按照上面的格式以此类推。 14 | 上海交易所=cn.finance.yahoo.com,.SS,Chinese,sl1d1t1c1ohgv 15 | 深圳交易所=cn.finance.yahoo.com,.SZ,Chinese,sl1d1t1c1ohgv 16 | 美国交易所=finance.yahoo.com,,United States,sl1d1t1c1ohgv 17 | 加拿大=ca.finance.yahoo.com,.TO,Toronto,sl1d1t1c1ohgv 18 | 新西兰=au.finance.yahoo.com,.NZ,sl1d1t1c1ohgv 19 | 新加坡=sg.finance.yahoo.com,.SI,Singapore,sl1d1t1c1ohgv 20 | 香港=hk.finance.yahoo.com,.HK,Hong Kong,sl1d1t1c1ohgv 21 | 台湾=tw.finance.yahoo.com,.TW,Taiwan,sl1d1t1c1ohgv 22 | 印度=in.finance.yahoo.com,.BO,Bombay,sl1d1t1c1ohgv 23 | 伦敦=uk.finance.yahoo.com,.L,London,sl1d1t1c1ohgv 24 | 澳洲=au.finance.yahoo.com,.AX,Sydney,sl1d1t1c1ohgv 25 | 巴西=br.finance.yahoo.com,.SA,Sao Paulo,sl1d1t1c1ohgv 26 | 瑞典=se.finance.yahoo.com,.ST,Stockholm,sl1d1t1c1ohgv 27 | -------------------------------------------------------------------------------- /quantdigger/backup/backtest/loader.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pandas as pd 3 | from pandas.io.data import DataReader 4 | from collections import OrderedDict 5 | import pytz 6 | from six import iteritems 7 | import logging 8 | 9 | from config import cache_path, data_path 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | def get_data_filepath(name): 14 | """ 15 | Returns a handle to data file. 16 | 17 | Creates containing directory, if needed. 18 | """ 19 | 20 | if not os.path.exists(data_path): 21 | os.makedirs(data_path) 22 | 23 | return os.path.join(data_path, name) 24 | 25 | 26 | def get_cache_filepath(name): 27 | if not os.path.exists(cache_path): 28 | os.makedirs(cache_path) 29 | 30 | return os.path.join(cache_path, name) 31 | 32 | 33 | def _load_raw_yahoo_data(indexes=None, stocks=None, start=None, end=None): 34 | """Load closing prices from yahoo finance. 35 | 36 | :Optional: 37 | indexes : dict (Default: {'SPX': '^GSPC'}) 38 | Financial indexes to load. 39 | stocks : list (Default: ['AAPL', 'GE', 'IBM', 'MSFT', 40 | 'XOM', 'AA', 'JNJ', 'PEP', 'KO']) 41 | Stock closing prices to load. 42 | start : datetime (Default: datetime(1993, 1, 1, 0, 0, 0, 0, pytz.utc)) 43 | Retrieve prices from start date on. 44 | end : datetime (Default: datetime(2002, 1, 1, 0, 0, 0, 0, pytz.utc)) 45 | Retrieve prices until end date. 46 | 47 | :Note: 48 | This is based on code presented in a talk by Wes McKinney: 49 | http://wesmckinney.com/files/20111017/notebook_output.pdf 50 | """ 51 | 52 | assert indexes is not None or stocks is not None, """ 53 | must specify stocks or indexes""" 54 | 55 | if start is None: 56 | start = pd.datetime(1990, 1, 1, 0, 0, 0, 0, pytz.utc) 57 | 58 | if start is not None and end is not None: 59 | assert start < end, "start date is later than end date." 60 | 61 | data = OrderedDict() 62 | 63 | if stocks is not None: 64 | for stock in stocks: 65 | print(stock) 66 | stock_pathsafe = stock.replace(os.path.sep, '--') 67 | cache_filename = "{stock}-{start}-{end}.csv".format( 68 | stock=stock_pathsafe, 69 | start=start, 70 | end=end).replace(':', '-') 71 | cache_filepath = get_cache_filepath(cache_filename) 72 | if os.path.exists(cache_filepath): 73 | stkd = pd.DataFrame.from_csv(cache_filepath) 74 | else: 75 | stkd = DataReader(stock, 'yahoo', start, end).sort_index() 76 | stkd.to_csv(cache_filepath) 77 | data[stock] = stkd 78 | 79 | if indexes is not None: 80 | for name, ticker in iteritems(indexes): 81 | print(name) 82 | stkd = DataReader(ticker, 'yahoo', start, end).sort_index() 83 | data[name] = stkd 84 | 85 | return data 86 | 87 | 88 | def load_from_yahoo(indexes=None, 89 | stocks=None, 90 | start=None, 91 | end=None, 92 | adjusted=True): 93 | """ 94 | Loads price data from Yahoo into a dataframe for each of the indicated 95 | securities. By default, 'price' is taken from Yahoo's 'Adjusted Close', 96 | which removes the impact of splits and dividends. If the argument 97 | 'adjusted' is False, then the non-adjusted 'close' field is used instead. 98 | 99 | :param indexes: Financial indexes to load. 100 | :type indexes: dict 101 | :param stocks: Stock closing prices to load. 102 | :type stocks: list 103 | :param start: Retrieve prices from start date on. 104 | :type start: datetime 105 | :param end: Retrieve prices until end date. 106 | :type end: datetime 107 | :param adjusted: Adjust the price for splits and dividends. 108 | :type adjusted: bool 109 | 110 | """ 111 | import ipdb; ipdb.set_trace() # BREAKPOINT 112 | data = _load_raw_yahoo_data(indexes, stocks, start, end) 113 | if adjusted: 114 | close_key = 'Adj Close' 115 | else: 116 | close_key = 'Close' 117 | df = pd.DataFrame({key: d[close_key] for key, d in iteritems(data)}) 118 | df.index = df.index.tz_localize(pytz.utc) 119 | return df 120 | 121 | 122 | def load_bars_from_yahoo(indexes=None, 123 | stocks=None, 124 | start=None, 125 | end=None, 126 | adjusted=True): 127 | """ 128 | Loads data from Yahoo into a panel with the following 129 | column names for each indicated security: 130 | 131 | - open 132 | - high 133 | - low 134 | - close 135 | - volume 136 | - price 137 | 138 | Note that 'price' is Yahoo's 'Adjusted Close', which removes the 139 | impact of splits and dividends. If the argument 'adjusted' is True, then 140 | the open, high, low, and close values are adjusted as well. 141 | 142 | :param indexes: Financial indexes to load. 143 | :type indexes: dict 144 | :param stocks: Stock closing prices to load. 145 | :type stocks: list 146 | :param start: Retrieve prices from start date on. 147 | :type start: datetime 148 | :param end: Retrieve prices until end date. 149 | :type end: datetime 150 | :param adjusted: Adjust open/high/low/close for splits and dividends. 151 | The 'price' field is always adjusted. 152 | :type adjusted: bool 153 | 154 | """ 155 | data = _load_raw_yahoo_data(indexes, stocks, start, end) 156 | panel = pd.Panel(data) 157 | # Rename columns 158 | panel.minor_axis = ['open', 'high', 'low', 'close', 'volume', 'price'] 159 | panel.major_axis = panel.major_axis.tz_localize(pytz.utc) 160 | # Adjust data 161 | if adjusted: 162 | adj_cols = ['open', 'high', 'low', 'close'] 163 | for ticker in panel.items: 164 | ratio = (panel[ticker]['price'] / panel[ticker]['close']) 165 | ratio_filtered = ratio.fillna(0).values 166 | for col in adj_cols: 167 | panel[ticker][col] *= ratio_filtered 168 | return panel 169 | 170 | -------------------------------------------------------------------------------- /quantdigger/backup/backtest/main.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Wenwei Huang' 2 | 3 | import logging 4 | from PyQt4 import QtGui 5 | 6 | from mainwindow import MainWindow 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | if __name__ == "__main__": 11 | logging.basicConfig() 12 | logging.getLogger().setLevel(logging.INFO) 13 | logger.info("Start") 14 | import sys 15 | app = QtGui.QApplication(sys.argv) 16 | mainWindow = MainWindow() 17 | mainWindow.show() 18 | sys.exit(app.exec_()) 19 | 20 | -------------------------------------------------------------------------------- /quantdigger/backup/backtest/mainwindow.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Wenwei Huang' 2 | 3 | import os 4 | import logging 5 | import glob 6 | import imp 7 | import pytz 8 | from datetime import datetime 9 | 10 | import pandas as pd 11 | import matplotlib.mlab as mlab 12 | from PyQt4 import QtCore 13 | from PyQt4 import QtGui 14 | from dateutil import parser 15 | import matplotlib.dates as mdates 16 | from functools import partial 17 | 18 | from config import data_path 19 | from config import strategy_path 20 | from utils import fromUtf8, WindowSize, sysopen 21 | from mainwindow_ui import Ui_MainWindow 22 | from strategy_runner import StrategyRunner 23 | from loader import load_from_yahoo, _load_raw_yahoo_data 24 | 25 | logger = logging.getLogger(__name__) 26 | 27 | class MainWindow(QtGui.QMainWindow): 28 | def __init__(self, parent=None): 29 | super(MainWindow, self).__init__(parent) 30 | self.ui_controller = Ui_MainWindow() 31 | self.ui_controller.setupUi(self) 32 | self.connect() 33 | self.ui_controller.matplotlibWidget.connect() 34 | self.init_style_menu() 35 | self.init_indicator_menu() 36 | self.init_strategy_panel() 37 | self.ui_controller.dateStartEdit.setDateTime(datetime(1990,1,1)) 38 | self.ui_controller.dateEndEdit.setDateTime(datetime.now()) 39 | self.df = None 40 | 41 | def init_style_menu(self): 42 | self.ui_controller.styleMenu = QtGui.QMenu(self) 43 | self.ui_controller.lineChartAction = QtGui.QAction("Line", self) 44 | self.ui_controller.areaChartAction = QtGui.QAction("Area", self) 45 | self.ui_controller.barChartAction = QtGui.QAction("Bar", self) 46 | self.ui_controller.candleChartAction = QtGui.QAction("Candle", self) 47 | self.ui_controller.styleMenu.addAction(self.ui_controller.lineChartAction) 48 | self.ui_controller.styleMenu.addAction(self.ui_controller.areaChartAction) 49 | self.ui_controller.styleMenu.addAction(self.ui_controller.barChartAction) 50 | self.ui_controller.styleMenu.addAction(self.ui_controller.candleChartAction) 51 | self.ui_controller.styleToolButton.setMenu(self.ui_controller.styleMenu) 52 | 53 | def init_indicator_menu(self): 54 | self.ui_controller.indicatorMenu = QtGui.QMenu(self) 55 | self.ui_controller.indicator_SMAAction = QtGui.QAction("Simple Moving Average (SMA)", self) 56 | self.ui_controller.indicator_EMAAction = QtGui.QAction("Exponential Moving Average (EMA)", self) 57 | self.ui_controller.indicator_MACDAction = QtGui.QAction("MACD", self) 58 | self.ui_controller.indicator_RSIAction = QtGui.QAction("Relative Strength Index (RSI)", self) 59 | self.ui_controller.indicatorMenu.addAction(self.ui_controller.indicator_SMAAction) 60 | self.ui_controller.indicatorMenu.addAction(self.ui_controller.indicator_EMAAction) 61 | self.ui_controller.indicatorMenu.addAction(self.ui_controller.indicator_MACDAction) 62 | self.ui_controller.indicatorMenu.addAction(self.ui_controller.indicator_RSIAction) 63 | self.ui_controller.indicatorToolButton.setMenu(self.ui_controller.indicatorMenu) 64 | 65 | def init_strategy_panel(self): 66 | strategy_files = sorted(glob.glob('%s/*.py' % strategy_path)) 67 | for file in strategy_files: 68 | base = os.path.splitext(os.path.basename(file))[0] 69 | item = QtGui.QListWidgetItem(base, self.ui_controller.strategyListWidget) 70 | item.setData(QtCore.Qt.UserRole, QtCore.QVariant(file)) 71 | self.ui_controller.strategyListWidget.addItem(item) 72 | self.ui_controller.strategyListWidget.customContextMenuRequested.connect(self.showMenu) 73 | 74 | def connect(self): 75 | for toolButton in self.ui_controller.buttonGroup.buttons(): 76 | toolButton.clicked.connect(partial(self.on_toolButtonClicked, toolButton)) 77 | self.ui_controller.actionRunStrategy.triggered.connect(self.on_runStrategy) 78 | self.ui_controller.actionEditStrategy.triggered.connect(self.on_editStrategy) 79 | self.ui_controller.symbolLineEdit.returnPressed.connect(self.load_symbol) 80 | self.ui_controller.symbolLineEdit.textChanged.connect(self.on_symbolEditChanged) 81 | 82 | def on_loadQuoteClicked(self): 83 | logger.info('load quote') 84 | 85 | fileName = QtGui.QFileDialog.getOpenFileName( 86 | self, self.tr("Open Quote Data"), data_path, 87 | self.tr("Quote Files (*.csv)")) 88 | logger.info("Filename %s" % fileName) 89 | 90 | if os.path.isfile(fileName): 91 | df = pd.read_csv(unicode(fileName)) 92 | df.columns = [col.lower() for col in df.columns] 93 | if 'datetime' in df.columns: 94 | df = df.sort(['datetime']) 95 | df['datetime'] = df.apply( 96 | lambda row: mdates.date2num(parser.parse(row['datetime'])), 97 | axis=1) 98 | elif 'date' in df.columns: 99 | df = df.sort(['date']) 100 | df['datetime'] = df.apply( 101 | lambda row: mdates.date2num(parser.parse(row['date'])), 102 | axis=1) 103 | 104 | if 'datetime' in df.columns and not df['datetime'].empty: 105 | self.ui_controller.matplotlibWidget.set_data(df) 106 | self.ui_controller.matplotlibWidget.draw_data() 107 | self.df = df 108 | 109 | def on_toolButtonClicked(self, button): 110 | name = str(button.objectName()) 111 | button_values = { 112 | 'oneDayToolButton': WindowSize.ONEDAY, 113 | 'fiveDayToolButton': WindowSize.FIVEDAY, 114 | 'oneMonthToolButton': WindowSize.ONEMONTH, 115 | 'threeMonthToolButton': WindowSize.THREEMONTH, 116 | 'sixMonthToolButton': WindowSize.SIXMONTH, 117 | 'oneYearToolButton': WindowSize.ONEYEAR, 118 | 'twoYearToolButton': WindowSize.TWOYEAR, 119 | 'fiveYearToolButton': WindowSize.FIVEYEAR, 120 | 'maxToolButton': WindowSize.MAX, 121 | } 122 | size = button_values[name] 123 | self.ui_controller.matplotlibWidget.setxlim(size) 124 | 125 | def showMenu(self, position): 126 | indexes = self.ui_controller.strategyListWidget.selectedIndexes() 127 | if len(indexes) > 0: 128 | menu = QtGui.QMenu() 129 | menu.addAction(self.ui_controller.actionRunStrategy) 130 | menu.addAction(self.ui_controller.actionEditStrategy) 131 | menu.exec_(self.ui_controller.strategyListWidget.viewport().mapToGlobal(position)) 132 | 133 | def on_runStrategy(self, check): 134 | indexes = self.ui_controller.strategyListWidget.selectedIndexes() 135 | if len(indexes) > 0: 136 | logger.info('Run strategy') 137 | index = indexes[0].row() 138 | item = self.ui_controller.strategyListWidget.item(index) 139 | strategy_file = str(item.data(QtCore.Qt.UserRole).toPyObject()) 140 | strategy = imp.load_source('strategy', strategy_file) 141 | if hasattr(strategy, 'initialize') and hasattr(strategy, 'run'): 142 | runner = StrategyRunner(initialize=strategy.initialize, run=strategy.run) 143 | runner.run(self.df) 144 | else: 145 | logger.error("%s is not a valid strategy" % strategy_file) 146 | 147 | def on_editStrategy(self, check): 148 | indexes = self.ui_controller.strategyListWidget.selectedIndexes() 149 | if len(indexes) > 0: 150 | logger.info('Edit strategy') 151 | index = indexes[0].row() 152 | item = self.ui_controller.strategyListWidget.item(index) 153 | strategy_file = item.data(QtCore.Qt.UserRole).toPyObject() 154 | sysopen(strategy_file) 155 | 156 | def on_symbolEditChanged(self, text): 157 | if text: 158 | self.ui_controller.symbolLineEdit.setText(str(text).upper()) 159 | 160 | def load_symbol(self): 161 | start = parser.parse(str(self.ui_controller.dateStartEdit.text())) 162 | end = parser.parse(str(self.ui_controller.dateEndEdit.text())) 163 | symbol = str(self.ui_controller.symbolLineEdit.text()) 164 | if not symbol: return 165 | data = _load_raw_yahoo_data(stocks=[symbol], indexes={}, 166 | start=start, end=end) 167 | self.df = data[symbol] 168 | self.df.columns = [col.lower() for col in self.df.columns] 169 | self.df['datetime'] = self.df.index 170 | self.df['datetime'] = self.df.apply( 171 | lambda row: mdates.date2num(row['datetime']), 172 | axis=1) 173 | if 'adj close' in self.df.columns: 174 | self.df['close'] = self.df['adj close'] 175 | 176 | self.ui_controller.matplotlibWidget.set_data(self.df) 177 | self.ui_controller.matplotlibWidget.draw_data() 178 | self.ui_controller.symbolLineEdit.setText('') 179 | 180 | 181 | -------------------------------------------------------------------------------- /quantdigger/backup/backtest/matplotlibwidget.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Wenwei Huang' 2 | 3 | from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg 4 | from matplotlib.figure import Figure 5 | from matplotlib.colors import colorConverter 6 | from matplotlib.collections import LineCollection, PolyCollection 7 | from matplotlib.finance import * 8 | from PyQt4 import QtCore 9 | from utils import fromUtf8 10 | import numpy as np 11 | import matplotlib.dates as mdates 12 | from datetime import date, time, datetime, timedelta 13 | 14 | from utils import WindowSize 15 | import config 16 | 17 | import logging 18 | logger = logging.getLogger(__name__) 19 | 20 | class SnaptoCursor(object): 21 | def __init__(self, ax): 22 | self.x, self.y = None, None 23 | self.lx = ax.axhline(color='k', linestyle=':') 24 | self.ly = ax.axvline(color='k', linestyle=':') 25 | props = dict(boxstyle='round', facecolor='white', alpha=0.5) 26 | self.txt = ax.text(0.005, 0.99, '', transform=ax.transAxes, name='monospace', 27 | size='smaller', va='top', bbox=props, alpha=0.5) 28 | 29 | def set_data(self, date, open, high, low, close, vol): 30 | self.open = open 31 | self.high = high 32 | self.low = low 33 | self.close = close 34 | self.vol = vol 35 | self.x = date 36 | self.y = self.close 37 | 38 | def mouse_move(self, event): 39 | if not event.inaxes: return 40 | if self.x is None or self.y is None: return 41 | x, y = event.xdata, event.ydata 42 | idx = np.searchsorted(self.x, x) 43 | if idx >= len(self.x): return 44 | x = self.x[idx] 45 | y = self.y[idx] 46 | # update the line positions self.ly.set_xdata(x) 47 | self.lx.set_ydata(y) 48 | self.ly.set_xdata(x) 49 | 50 | text = [] 51 | open = self.open[idx] if self.open is not None and idx < len(self.open) else None 52 | close = self.close[idx] if self.close is not None and idx < len(self.close) else None 53 | high = self.high[idx] if self.high is not None and idx < len(self.high) else None 54 | low = self.low[idx] if self.low is not None and idx < len(self.low) else None 55 | vol = self.vol[idx] if self.vol is not None and idx < len(self.vol) else None 56 | day = mdates.num2date(x) 57 | if day.time() == time(0,0): 58 | date_str = datetime.strftime(day, '%b %d %Y') 59 | else: 60 | date_str = datetime.strftime(day, '%b %d %Y %H:%M:%S') 61 | text.append("{0:>5s} {1:<12s}".format('Date', date_str)) 62 | if open: 63 | text.append("{0:>5s} {1:.2f}".format('Open', open)) 64 | if close: 65 | text.append("{0:>5s} {1:.2f}".format('Close', close)) 66 | if high: 67 | text.append("{0:>5s} {1:.2f}".format('High', high)) 68 | if low: 69 | text.append("{0:>5s} {1:.2f}".format('Low', low)) 70 | if vol: 71 | text.append("{0:>5s} {1:.2f}M".format('Vol', (float(vol)/1000000))) 72 | self.txt.set_text('\n'.join(text)) 73 | 74 | def cleanup(self): 75 | try: 76 | self.x, self.y = None, None 77 | self.lx.remove() 78 | self.ly.remove() 79 | self.txt.remove() 80 | except ValueError: 81 | import traceback 82 | logger.warn(traceback.format_exc()) 83 | 84 | class PointMarker(object): 85 | def __init__(self, ax, color='k'): 86 | self.marker, = ax.plot(-1, -1, 'o', color=color, alpha=0.5, zorder=10) 87 | 88 | def set_data(self, x, y): 89 | self.x = x 90 | self.y = y 91 | 92 | def mouse_move(self, event): 93 | if not event.inaxes: return 94 | if self.x is None or self.y is None: return 95 | x, y = event.xdata, event.ydata 96 | idx = np.searchsorted(self.x, x) 97 | if idx >= len(self.x): return 98 | x = self.x[idx] 99 | y = self.y[idx] 100 | self.marker.set_xdata(x) 101 | self.marker.set_ydata(y) 102 | 103 | def cleanup(self): 104 | try: 105 | self.marker.remove() 106 | except ValueError: 107 | import traceback 108 | logger.warn(traceback.format_exc()) 109 | 110 | class VolumeBars(object): 111 | def __init__(self, ax, dates, opens, closes, volumes): 112 | self.dates = dates 113 | self.opens = opens 114 | self.closes = closes 115 | self.volumes = [float(v)/1e6 for v in volumes] 116 | self.ax = ax 117 | 118 | def add_bars(self, colorup='g', colordown='r', alpha=0.5, width=1): 119 | r,g,b = colorConverter.to_rgb(colorup) 120 | colorup = r,g,b,alpha 121 | r,g,b = colorConverter.to_rgb(colordown) 122 | colordown = r,g,b,alpha 123 | colord = {True: colorup, False: colordown} 124 | colors = [colord[open xmin or max(self.main_x) < xmax: return 276 | 277 | self.axes.set_xlim([xmin, xmax]) 278 | self.adjust_ylim(xmin, xmax) 279 | 280 | -------------------------------------------------------------------------------- /quantdigger/backup/backtest/resource.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | icons/edit.png 4 | icons/run.png 5 | 6 | 7 | -------------------------------------------------------------------------------- /quantdigger/backup/backtest/strategy/demo.py: -------------------------------------------------------------------------------- 1 | 2 | def initialize(context): 3 | print context 4 | 5 | def run(data): 6 | print data 7 | -------------------------------------------------------------------------------- /quantdigger/backup/backtest/strategy_runner.py: -------------------------------------------------------------------------------- 1 | 2 | class StrategyRunner(object): 3 | def __init__(self, **kwargs): 4 | self._run = kwargs.get('run', None) 5 | self._initialize = kwargs.get('initialize', None) 6 | 7 | def run(self, data): 8 | self._run(data) 9 | 10 | 11 | -------------------------------------------------------------------------------- /quantdigger/backup/backtest/utils.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Wenwei Huang' 2 | 3 | 4 | from PyQt4 import QtCore 5 | import os, sys 6 | 7 | try: 8 | _fromUtf8 = QtCore.QString.fromUtf8 9 | except AttributeError: 10 | def _fromUtf8(s): 11 | return s 12 | 13 | fromUtf8 = _fromUtf8 14 | 15 | class WindowSize(object): 16 | ONEDAY = 'ONEDAY' 17 | FIVEDAY = 'FIVEDAY' 18 | ONEMONTH = 'ONEMONTH' 19 | THREEMONTH = 'THREEMONTH' 20 | SIXMONTH = 'SIXMONTH' 21 | ONEYEAR = 'ONEYEAR' 22 | TWOYEAR = 'TWOYEAR' 23 | FIVEYEAR = 'FIVEYEAR' 24 | MAX = 'MAX' 25 | 26 | 27 | def sysopen(filename): 28 | if os.name == 'nt': 29 | os.startfile(filename) 30 | elif sys.platform.startswith('darwin'): 31 | os.system('open %s' % filename) 32 | elif os.name == 'posix': 33 | os.system('xdg-open %s' % filename) 34 | -------------------------------------------------------------------------------- /quantdigger/backup/demo/3d.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/quantdigger/backup/demo/3d.py -------------------------------------------------------------------------------- /quantdigger/backup/demo/entry_3d.py: -------------------------------------------------------------------------------- 1 | #import xlrd 2 | #data = xlrd.open_workbook('1.xls') 3 | #table = data.sheets()[0] 4 | #for i in range(table.nrows): 5 | #print table.row_values(i) 6 | 7 | import pandas as pd 8 | from mpl_toolkits.mplot3d import Axes3D 9 | import numpy as np 10 | import matplotlib 11 | matplotlib.use("TKAgg") 12 | import matplotlib.pyplot as plt 13 | from itertools import groupby 14 | from pprint import pprint 15 | from matplotlib.widgets import Cursor 16 | fname = raw_input("Please input file name: ") 17 | data2 = pd.read_csv(fname) 18 | data2 = data2.ix[:, 2:-1] 19 | def chunks(l, n): 20 | """ Yield successive n-sized chunks from l. 21 | """ 22 | for i in xrange(0, len(l), n): 23 | yield l[i:i+n] 24 | 25 | 26 | #def group(ylist, dist=0): 27 | #'''''' 28 | #ylist.sort() 29 | #groups = { } 30 | #uni = sorted(list(set(ylist))) 31 | #uni = list(chunks(uni, 5)) 32 | #found = False 33 | #gd = [] 34 | #preseed = uni[0][len(uni[0])/2] 35 | #for d in ylist: 36 | #for g in uni: 37 | ## find the right group 38 | #if d in g: 39 | #seed = g[len(g)/2] 40 | #if preseed != seed: 41 | #groups[preseed] = gd 42 | #gd = [] 43 | #gd.append(d) 44 | #preseed = seed 45 | #break 46 | #groups[seed] = gd 47 | #return groups 48 | 49 | 50 | def process(data2): 51 | '''docstring for process''' 52 | xlist = [] 53 | ylist = [] 54 | zlist = [] 55 | #print groups.keys() 56 | for i in range(len(data2.columns)): 57 | zi = data2.ix[:, i] 58 | #groups = group(zi.tolist()) 59 | n, bins = np.histogram(zi.tolist(), 40) 60 | xlist.append(i+1) 61 | ylist.append(bins[:-1]) 62 | zlist.append(n) 63 | return xlist, ylist, zlist 64 | 65 | 66 | fig = plt.figure() 67 | ax = fig.add_subplot(211, projection='3d') 68 | ax2 = fig.add_subplot(212) 69 | ax2.autoscale_view() 70 | 71 | #x = data2.c.astype(float).tolist() 72 | #y = data2.b.astype(float).tolist() 73 | #z = data2.k.astype(float).tolist() 74 | x, y, z = process(data2) 75 | lines = [] 76 | l2d, = ax2.plot([0]*0) 77 | 78 | def on_pick(event): 79 | '''docstring for on_motion''' 80 | global l2d 81 | for i,line in enumerate(lines): 82 | # code... 83 | if line != event.artist: 84 | line.set_visible(False) 85 | else: 86 | w = i 87 | 88 | ax2.set_xlabel("%s"%(w+1)) 89 | l2d.remove() 90 | l2d, = ax2.plot(y[w], z[w], 'ko') 91 | fig.canvas.draw_idle() 92 | print str(event.mouseevent.xdata) 93 | 94 | def on_button_release(event): 95 | for line in lines: 96 | line.set_visible(True) 97 | fig.canvas.draw_idle() 98 | 99 | for i in range(len(x)): 100 | lines.append(ax.scatter([x[i]]*len(y[i]), y[i], z[i], color=['red']*len(y[i]), picker=1)) 101 | 102 | fig.canvas.mpl_connect('pick_event', on_pick) 103 | fig.canvas.mpl_connect('button_release_event', on_button_release) 104 | c1 = Cursor(ax2, useblit=True, color='red', linewidth=1, vertOn = True, horizOn = True) 105 | 106 | #ax.set_ylim3d(min(y), max(y)) 107 | #ax.set_zlim3d(min(z), max(z)) 108 | #ax.set_xticklabels(range(len(data2.columns)+1)) 109 | 110 | plt.show() 111 | 112 | -------------------------------------------------------------------------------- /quantdigger/backup/demo/k.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import matplotlib 3 | matplotlib.use('TkAgg') 4 | import os 5 | import matplotlib.pyplot as plt 6 | from matplotlib.widgets import Cursor 7 | from mplot_widgets.widgets import * 8 | from datasource.data import get_stock_signal_data 9 | 10 | 11 | fig = plt.figure(facecolor='white') 12 | axk = plt.axes([0.1, 0.2, 0.8, 0.7], axisbg='k') 13 | axk.grid(True) 14 | xslider = plt.axes([0.1, 0.1, 0.8, 0.03]) 15 | #yslider = plt.axes([0.1, 0.05, 0.8, 0.03]) 16 | #ax.xaxis.set_minor_formatter(dayFormatter) 17 | 18 | 19 | price_data, entry_x, entry_y, exit_x, exit_y, colors = get_stock_signal_data() 20 | 21 | slw = 2 22 | # setup windows 23 | print("plotting.......") 24 | observer_slider = Slider(xslider, "slider", '', 0, len(price_data), len(price_data), len(price_data)/100, "%d") 25 | kwindow = CandleWindow(axk, "kwindow", price_data, 100, 50) 26 | 27 | kwindow.on_changed(observer_slider) 28 | observer_slider.on_changed(kwindow) 29 | #signal = SignalWindow(axk, zip(zip(entry_x,entry_y),zip(exit_x,exit_y)), colors, slw) 30 | c1 = Cursor(axk, useblit=True, color='white', linewidth=1, vertOn = True, horizOn = True) 31 | plt.show() 32 | 33 | -------------------------------------------------------------------------------- /quantdigger/backup/demo/pick.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | fig = plt.figure() 4 | ax = fig.add_subplot(111) 5 | ax.set_title('click on points') 6 | line, = ax.plot(np.random.rand(100), 'o', picker=5) # 5 points tolerance 7 | print id(line) 8 | display = True 9 | 10 | 11 | def enter_axes(event): 12 | """docstring for enter_axes""" 13 | print "hello" 14 | 15 | def onpick(event): 16 | global display 17 | thisline = event.artist # == line 18 | print id(thisline) 19 | xdata = thisline.get_xdata() # Point Set 20 | ydata = thisline.get_ydata() 21 | if display: 22 | ax.set_visible(False) 23 | display = False 24 | else: 25 | ax.set_visible(True) 26 | display = True 27 | ind = event.ind 28 | fig.canvas.draw() 29 | print 'onpick points:', zip(xdata[ind], ydata[ind]) 30 | 31 | fig.canvas.mpl_connect('pick_event', onpick) 32 | fig.canvas.mpl_connect('axes_enter_event', enter_axes) 33 | 34 | plt.show() 35 | -------------------------------------------------------------------------------- /quantdigger/backup/demo/selctor.py: -------------------------------------------------------------------------------- 1 | from matplotlib.widgets import RectangleSelector 2 | from pylab import * 3 | 4 | def onselect(eclick, erelease): 5 | 'eclick and erelease are matplotlib events at press and release' 6 | print ' startposition : (%f, %f)' % (eclick.xdata, eclick.ydata) 7 | print ' endposition : (%f, %f)' % (erelease.xdata, erelease.ydata) 8 | print ' used button : ', eclick.button 9 | 10 | def toggle_selector(event): 11 | print ' Key pressed.' 12 | if event.key in ['Q', 'q'] and toggle_selector.RS.active: 13 | print ' RectangleSelector deactivated.' 14 | toggle_selector.RS.set_active(False) 15 | if event.key in ['A', 'a'] and not toggle_selector.RS.active: 16 | print ' RectangleSelector activated.' 17 | toggle_selector.RS.set_active(True) 18 | 19 | x = arange(100)/(99.0) 20 | y = sin(x) 21 | fig = figure 22 | ax = subplot(111) 23 | ax.plot(x,y) 24 | 25 | toggle_selector.RS = RectangleSelector(ax, onselect, drawtype='line') 26 | connect('key_press_event', toggle_selector) 27 | show() 28 | -------------------------------------------------------------------------------- /quantdigger/backup/demo/subplot.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | fig = plt.figure() 3 | ax1 = fig.add_subplot(311) 4 | ax2 = fig.add_subplot(312) 5 | ax3 = fig.add_subplot(313) 6 | ax4 = fig.add_axes([0.1, 0.5, 0.9, 0.3], frameon = False, visible = True) 7 | ax4.plot([1,2,3,4,5,6]) 8 | fig.subplots_adjust(top = 1, bottom = 0, hspace = 0) 9 | 10 | #ax1 = plt.subplot2grid((3, 3), (0, 0)) 11 | #ax2 = plt.subplot2grid((3, 3), (0, 1), colspan = 2) 12 | #ax3 = plt.subplot2grid((3, 3), (1, 0), colspan = 2, rowspan = 2) 13 | #ax4 = plt.subplot2grid((3, 3), (1, 2), rowspan = 2) 14 | #plt.subplots_adjust(top = 1, bottom = 0, hspace = 0) 15 | #print len(ax4.figure.axes) 16 | 17 | 18 | plt.show() 19 | -------------------------------------------------------------------------------- /quantdigger/backup/demo/summary.py: -------------------------------------------------------------------------------- 1 | # -*- coding: gbk -*- 2 | import matplotlib 3 | matplotlib.use('TkAgg') 4 | import matplotlib.pyplot as plt 5 | import analyze 6 | 7 | def wave_analyze(fname): 8 | #print waves 9 | #load_data(0, fname) 10 | data, = load_datas(0, False, fname) 11 | t, = load_wavedata(fname) 12 | 13 | def tradeinfo(fname, n=10, intraday=False): 14 | '''docstring for analyze''' 15 | analyze.AnalyzeFrame(fname, 10, intraday) 16 | plt.show() 17 | 18 | if __name__ == '__main__': 19 | tradeinfo("_djtrend2_IF000") 20 | 21 | -------------------------------------------------------------------------------- /quantdigger/backup/demo/tech.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import matplotlib 3 | matplotlib.use('TkAgg') 4 | import matplotlib.pyplot as plt 5 | import os 6 | #from matplotlib.widgets import Cursor 7 | from widgets import * 8 | from data import csv2frame, load_tradeinfo 9 | import mplotw 10 | 11 | 12 | # prepare data 13 | def get_stock_signal_data(): 14 | fname = os.path.join(os.getcwd(), 'data', 'stock_data', '_IF000.csv') 15 | price_data = csv2frame(fname) 16 | from matplotlib.colors import colorConverter 17 | info = load_tradeinfo("_djtrend2_IF000") 18 | entry_x = [] 19 | entry_y = info['entry_price'].tolist() 20 | exit_x = [] 21 | exit_y = info['exit_price'].tolist() 22 | colors = [] 23 | for t in info.index: 24 | entry_x.append(price_data.index.searchsorted(t)) 25 | for t in info['exit_datetime'].values: 26 | exit_x.append(price_data.index.searchsorted(t)) 27 | for i in range(len(info)): 28 | tr = info.ix[i] 29 | if tr['islong']: 30 | c = 'r' if tr['exit_price']>tr['entry_price'] else 'w' 31 | else: 32 | c = 'r' if tr['exit_price'] the_max: 89 | the_max = temp_max 90 | return the_min, the_max 91 | 92 | 93 | def get_random_series_from(data_frame): 94 | # 95 | size = len(data_frame) 96 | random_idx = random.randint(0, size-1) 97 | # 98 | return data_frame.ix[random_idx] 99 | 100 | 101 | def get_random_series_list_from(data_frame): 102 | # 103 | size = len(data_frame) 104 | random_idx = random.randint(0, size-1) 105 | # 106 | return data_frame[random_idx:random_idx+1] 107 | -------------------------------------------------------------------------------- /quantdigger/backup/pyqt_kline/QuantView.py: -------------------------------------------------------------------------------- 1 | #_*_ coding: utf-8 _*_ 2 | ## @todo merge quantview to ContainerView 3 | 4 | __author__ = 'TeaEra' 5 | from PyQt4.QtGui import * 6 | from PyQt4.QtCore import * 7 | 8 | from QuantUtils import from_utf8 9 | from QuantUtils import translate 10 | from QuantUtils import get_random_series_list_from 11 | from TrendsWidget import ContainerView 12 | 13 | class MainForm(QWidget): 14 | """ 15 | Class: MainWidget 16 | """ 17 | 18 | def __init__(self): 19 | super(MainForm, self).__init__() 20 | # 21 | self._quotation_view = None 22 | self._container_view = None 23 | self._menus = list() 24 | # 25 | self.init_main_form() 26 | 27 | def init_main_form(self): 28 | self.setWindowTitle( 29 | translate("Form", "TE-K-Line", None) 30 | ) 31 | # Layout for MainForm: 32 | main_form_grid_layout = QGridLayout(self) 33 | main_form_grid_layout.setContentsMargins(5, 0, 5, 5) 34 | # Tab widget: 35 | tab_widget = QTabWidget(self) 36 | tab_widget.setTabPosition(QTabWidget.North) 37 | # Menu bar: 38 | menu_bar_for_tab_widget = QMenuBar(self) 39 | # Add tab_widget & tab_k_line_menu_bar: 40 | main_form_grid_layout.addWidget(tab_widget, 1, 0, 1, 1) 41 | main_form_grid_layout.addWidget(menu_bar_for_tab_widget, 0, 0, 1, 1) 42 | # 43 | self.init_tab_widget(tab_widget) 44 | # 45 | self.init_menu_bar(menu_bar_for_tab_widget) 46 | # 47 | QObject.connect( 48 | tab_widget, 49 | SIGNAL("currentChanged(int)"), 50 | self.enable_menu_for_tab 51 | ) 52 | 53 | def enable_menu_for_tab(self, idx): 54 | """ 根据tab调整menu """ 55 | self._menus[idx].setEnabled(True) 56 | for i in range(len(self._menus)): 57 | if i != idx: 58 | self._menus[i].setEnabled(False) 59 | 60 | def init_tab_widget(self, tab_widget): 61 | # 蜡烛线 62 | tab_k_line_view = QWidget() 63 | tab_k_line_view_grid_layout = QGridLayout(tab_k_line_view) 64 | tab_widget.addTab(tab_k_line_view, from_utf8("K-Line")) 65 | container_view = ContainerView() 66 | self._container_view = container_view 67 | tab_k_line_view_grid_layout.addWidget( container_view, 0, 0, 1, 1) 68 | QObject.connect( 69 | container_view, 70 | SIGNAL("updateSeries(PyQt_PyObject)"), 71 | container_view.update_k_line 72 | ) 73 | QObject.connect( 74 | container_view, 75 | SIGNAL("appendDataFrame(PyQt_PyObject)"), 76 | container_view.append_k_line 77 | ) 78 | 79 | def init_menu_bar(self, menu_bar): 80 | # 81 | # Add menu: 82 | menu_new_k_line = menu_bar.addMenu("K-Line") 83 | # 84 | menu_new_k_line.setEnabled(True) 85 | self._menus.append(menu_new_k_line) 86 | # 87 | # Menu for 'K-Line': 88 | new_action_load = menu_new_k_line.addAction("Load data file") 89 | menu_new_k_line.addSeparator() 90 | new_action_update = menu_new_k_line.addAction("Update last k-line") 91 | new_action_append = menu_new_k_line.addAction("Append one k-line") 92 | menu_new_k_line.addSeparator() 93 | new_action_show_average_line = \ 94 | menu_new_k_line.addAction("Show average line") 95 | action_hide_average_line = \ 96 | menu_new_k_line.addAction("Hide average line") 97 | new_action_update.setEnabled(False) 98 | new_action_append.setEnabled(False) 99 | new_action_show_average_line.setEnabled(False) 100 | action_hide_average_line.setEnabled(False) 101 | # 102 | QObject.connect( new_action_load, 103 | SIGNAL("triggered()"), 104 | lambda x=[ 105 | new_action_update, new_action_append, 106 | new_action_show_average_line 107 | ]: 108 | self.on_load_data(x)) 109 | QObject.connect( new_action_update, 110 | SIGNAL("triggered()"), 111 | self.on_update) 112 | QObject.connect( new_action_append, 113 | SIGNAL("triggered()"), 114 | self.on_append) 115 | QObject.connect( new_action_show_average_line, 116 | SIGNAL("triggered()"), 117 | lambda x=[ 118 | new_action_show_average_line, action_hide_average_line 119 | ]: 120 | self.show_average_line(x)) 121 | QObject.connect( action_hide_average_line, 122 | SIGNAL("triggered()"), 123 | lambda x=[ 124 | new_action_show_average_line, action_hide_average_line 125 | ]: 126 | self.hide_average_line(x)) 127 | 128 | 129 | def on_load_data(self, action_list): 130 | file_dialog = QFileDialog(self) 131 | file_dialog.setWindowTitle("Load data file") 132 | file_dialog.setNameFilter("Data files (*.csv)") 133 | file_dialog.show() 134 | if file_dialog.exec_(): # Click 'Open' will return 1; 135 | data_file = file_dialog.selectedFiles()[0] 136 | self._container_view.load_data(data_file) 137 | for action in action_list: 138 | action.setEnabled(True) 139 | 140 | def on_update(self): 141 | data = get_random_series_list_from(self._container_view.get_k_line().get_data()) 142 | self._container_view.emit(SIGNAL("updateSeries(PyQt_PyObject)"), data) 143 | 144 | def on_append(self): 145 | data = get_random_series_list_from(self._container_view.get_k_line().get_data()) 146 | self._container_view.emit(SIGNAL("appendDataFrame(PyQt_PyObject)"), data) 147 | 148 | def show_average_line(self, menu): 149 | self._container_view.show_average_line() 150 | menu[0].setEnabled(False) 151 | menu[1].setEnabled(True) 152 | 153 | def hide_average_line(self, menu): 154 | self._container_view.hide_average_line() 155 | menu[0].setEnabled(True) 156 | menu[1].setEnabled(False) 157 | -------------------------------------------------------------------------------- /quantdigger/backup/pyqt_kline/QuoteWidget.py: -------------------------------------------------------------------------------- 1 | from PyQt4.QtGui import * 2 | from PyQt4.QtCore import * 3 | class QuoteWidget(QTableView): 4 | """ 5 | """ 6 | 7 | def __init__( 8 | self, data=None, 9 | horizontal_header_info=None, 10 | vertical_header_info=None 11 | ): 12 | super(QuoteWidget, self).__init__() 13 | self._model = QStandardItemModel() 14 | self.setModel(self._model) 15 | self._is_data_loaded = False 16 | # 17 | if data: 18 | self.set_data(data) 19 | # 20 | if horizontal_header_info: 21 | self.set_horizontal_header(horizontal_header_info) 22 | # 23 | if vertical_header_info: 24 | self.set_vertical_header(vertical_header_info) 25 | 26 | def clean_data(self): 27 | self._model.removeRows(0, self._model.rowCount()) 28 | 29 | def remove_data(self): 30 | self.clean_data() 31 | self.horizontalHeader().hide() 32 | self.verticalHeader().hide() 33 | 34 | def set_data(self, data): 35 | # 36 | self.clean_data() 37 | # 38 | for each_row in data: 39 | each_row_items = \ 40 | [ 41 | QStandardItem(from_utf8(str(item))) 42 | for item in each_row 43 | ] 44 | self._model.appendRow(each_row_items) 45 | # 46 | self._is_data_loaded = True 47 | self.show_header() 48 | 49 | def set_horizontal_header(self, horizontal_header_info): 50 | for i in range(len(horizontal_header_info)): 51 | self._model.setHeaderData( 52 | i, Qt.Horizontal, 53 | QVariant(from_utf8(str(horizontal_header_info[i]))) 54 | ) 55 | 56 | def set_vertical_header(self, vertical_header_info): 57 | for i in range(len(vertical_header_info)): 58 | self._model.setHeaderData( 59 | i, Qt.Vertical, 60 | QVariant(from_utf8(str(vertical_header_info[i]))) 61 | ) 62 | 63 | def set_data_color(self): 64 | for i in range(self._model.rowCount()): 65 | for j in range(self._model.columnCount()): 66 | self._model.setData( 67 | self._model.index(i, j), 68 | QColor(Qt.black), 69 | Qt.BackgroundRole 70 | ) 71 | self._model.setData( 72 | self._model.index(i, j), 73 | QColor(Qt.yellow), 74 | Qt.ForegroundRole 75 | ) 76 | 77 | def show_header(self): 78 | self.horizontalHeader().show() 79 | self.verticalHeader().show() 80 | 81 | def enable_header_sorting(self): 82 | self.setSortingEnabled(True) 83 | 84 | def enable_popup_context_menu(self): 85 | horizontal_header = self.horizontalHeader() 86 | vertical_header = self.verticalHeader() 87 | #horizontal_header.setClickable(True) 88 | #vertical_header.setClickable(True) 89 | horizontal_header.setContextMenuPolicy(Qt.CustomContextMenu) 90 | vertical_header.setContextMenuPolicy(Qt.CustomContextMenu) 91 | horizontal_header.customContextMenuRequested.connect( 92 | self.popup_context_menu 93 | ) 94 | vertical_header.customContextMenuRequested.connect( 95 | self.popup_context_menu 96 | ) 97 | 98 | def popup_context_menu(self, position): 99 | menu = QMenu() 100 | all_cols = [] 101 | for i in range(self._model.columnCount()): 102 | all_cols.append( 103 | menu.addAction( 104 | self._model.headerData( 105 | i, Qt.Horizontal 106 | ).toString() 107 | ) 108 | ) 109 | #menu.actions()[i] 110 | # Add a separator: 111 | menu.addSeparator() 112 | all_cols.append(menu.addAction("Show all")) 113 | all_cols.append(menu.addAction("Hide all")) 114 | ac = menu.exec_(self.mapToGlobal(position)) 115 | if ac in all_cols: 116 | idx = all_cols.index(ac) 117 | if idx < len(all_cols) - 2: 118 | if self.isColumnHidden(idx): 119 | self.showColumn(idx) 120 | elif not self.isColumnHidden(idx): 121 | self.hideColumn(idx) 122 | elif idx == len(all_cols) - 2: 123 | for i in range(self._model.columnCount()): 124 | self.showColumn(i) 125 | elif idx == len(all_cols) - 1: 126 | for i in range(self._model.columnCount()): 127 | self.hideColumn(i) 128 | 129 | def update_data(self, updated_data): 130 | for i in range(len(updated_data)): 131 | for j in range(len(updated_data[0])): 132 | if self._model.item(i, j).text()\ 133 | .compare( 134 | from_utf8(str(updated_data[i][j])) 135 | ) != 0: 136 | self._model.setData( 137 | self._model.index(i, j), 138 | QVariant(from_utf8(str(updated_data[i][j]))) 139 | ) 140 | self._model.setData( 141 | self._model.index(i, j), 142 | QColor(Qt.red), Qt.BackgroundRole 143 | ) 144 | self._model.setData( 145 | self._model.index(i, j), 146 | QColor(Qt.black), Qt.ForegroundRole 147 | ) 148 | 149 | def is_data_loaded(self): 150 | return self._is_data_loaded 151 | -------------------------------------------------------------------------------- /quantdigger/backup/pyqt_kline/TrendsWidget.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/quantdigger/backup/pyqt_kline/TrendsWidget.py -------------------------------------------------------------------------------- /quantdigger/backup/pyqt_kline/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'TeaEra' 2 | -------------------------------------------------------------------------------- /quantdigger/backup/pyqt_kline/_no_data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/quantdigger/backup/pyqt_kline/_no_data.png -------------------------------------------------------------------------------- /quantdigger/backup/pyqt_kline/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __author__ = 'TeaEra' 4 | 5 | from PyQt4 import QtGui 6 | 7 | from QuantView import MainForm 8 | 9 | ################################################################################ 10 | # Main portal: 11 | if __name__ == "__main__": 12 | print(">>> Main portal") 13 | # 14 | import sys 15 | app = QtGui.QApplication(sys.argv) 16 | main_form = MainForm() 17 | main_form.show() 18 | # 19 | # Move to center; 20 | curr_desktop_center = \ 21 | QtGui.QApplication.desktop().availableGeometry(main_form).center() 22 | main_form.move( 23 | curr_desktop_center.x() - main_form.width()*0.5, 24 | curr_desktop_center.y() - main_form.height()*0.5 25 | ) 26 | sys.exit(app.exec_()) -------------------------------------------------------------------------------- /quantdigger/backup/pyqt_kline/readme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/quantdigger/backup/pyqt_kline/readme -------------------------------------------------------------------------------- /quantdigger/backup/techmplot2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | from matplotlib.widgets import Cursor 3 | from matplotlib.widgets import MultiCursor 4 | import matplotlib.pyplot as plt 5 | 6 | class TechMPlot(object): 7 | def __init__(self, *args): 8 | self.fig = plt.figure() 9 | self.cross_cursor = None 10 | self.v_cursor = None 11 | self.add_subplot(*args) 12 | self.in_qt = False 13 | for ax in self.axes: 14 | ax.format_coord = self.format_coord 15 | self.connect() 16 | 17 | def init_qt(self): 18 | """docstring for set_qt""" 19 | self.in_qt = True 20 | if len(self.axes) == 1: 21 | self.cross_cursor = Cursor(self.axes[0], useblit=True, color='red', linewidth=2, vertOn=True, horizOn=True) 22 | else: 23 | self.v_cursor = MultiCursor(self.fig.canvas, self.fig.axes, color='r', lw=2, horizOn=False, vertOn=True) 24 | 25 | def connect(self): 26 | """ 27 | matplotlib信号连接。 28 | """ 29 | self.cidpress = self.fig.canvas.mpl_connect( "button_press_event", self.on_press) 30 | self.cidrelease = self.fig.canvas.mpl_connect( "button_release_event", self.on_release) 31 | self.cidmotion = self.fig.canvas.mpl_connect( "motion_notify_event", self.on_motion) 32 | 33 | self.fig.canvas.mpl_connect('axes_enter_event', self.enter_axes) 34 | self.fig.canvas.mpl_connect('axes_leave_event', self.leave_axes) 35 | 36 | def disconnect(self): 37 | self.fig.canvas.mpl_disconnect(self.cidmotion) 38 | self.fig.canvas.mpl_disconnect(self.cidrelease) 39 | self.fig.canvas.mpl_disconnect(self.cidpress) 40 | 41 | 42 | def on_press(self, event): 43 | print("press---") 44 | pass 45 | 46 | def on_release(self, event): 47 | print("release---") 48 | pass 49 | 50 | def on_motion(self, event): 51 | pass 52 | 53 | 54 | def enter_axes(self, event): 55 | #event.inaxes.patch.set_facecolor('yellow') 56 | # 只有当前axes会闪烁。 57 | if not self.in_qt: 58 | axes = [ax for ax in self.fig.axes if ax is not event.inaxes] 59 | self.v_cursor = MultiCursor(event.canvas, axes, color='r', lw=2, horizOn=False, vertOn=True) 60 | self.cross_cursor = Cursor(event.inaxes, useblit=True, color='red', linewidth=2, vertOn=True, horizOn=True) 61 | event.canvas.draw() 62 | print("enter---") 63 | 64 | def leave_axes(self, event): 65 | if not self.in_qt: 66 | self.v_cursor = None 67 | self.cross_cursor = None 68 | event.canvas.draw() # 在qt中不起作用,还影响流畅。 69 | print("leave---") 70 | 71 | def add_subplot(self, *args): 72 | num_axes = sum(args) 73 | for i, ratio in enumerate(args): 74 | if i > 0: 75 | plt.subplot2grid((num_axes, 1), (sum(args[:i]), 0), 76 | rowspan = ratio, sharex = self.fig.axes[0]) 77 | else: 78 | plt.subplot2grid((num_axes, 1), (sum(args[:i]), 0), rowspan = ratio) 79 | 80 | #self.slider = mwidgets.Slider(xslider, "slider", '', 0, len(price_data), len(price_data), len(price_data)/100, "%d") 81 | ##kwindow.on_changed(observer_slider) 82 | ##observer_slider.on_changed(kwindow) 83 | #signal = SignalWindow(axk, zip(zip(entry_x,entry_y),zip(exit_x,exit_y)), colors, slw) 84 | 85 | 86 | def subplots_adjust(self, left, bottom, right, top, wspace=None, hspace=None): 87 | plt.subplots_adjust(left, bottom, right, top, wspace, hspace) 88 | 89 | @property 90 | def axes(self): 91 | return self.fig.axes 92 | 93 | def format_coord(self, x, y): 94 | """ 状态栏信息显示 """ 95 | return "x=%.2f, y=%.2f" % (x, y) 96 | 97 | def draw_axes(self, ith, func): 98 | """传递绘图函数,画第ith个图。 99 | 100 | Args: 101 | ith (number): 待绘axes的编号。 102 | func (function): 绘图函数。 103 | """ 104 | try: 105 | axes = self.axes[ith] 106 | except IndexError as e: 107 | print(e) 108 | else: 109 | func(axes) 110 | 111 | -------------------------------------------------------------------------------- /quantdigger/backup/zipline 2014-1-17/zipline2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/quantdigger/backup/zipline 2014-1-17/zipline2.pdf -------------------------------------------------------------------------------- /quantdigger/backup/zipline 2014-1-17/zipline3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/quantdigger/backup/zipline 2014-1-17/zipline3.pdf -------------------------------------------------------------------------------- /quantdigger/demo/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/quantdigger/demo/.DS_Store -------------------------------------------------------------------------------- /quantdigger/demo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/quantdigger/demo/__init__.py -------------------------------------------------------------------------------- /quantdigger/demo/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | from quantdigger.kernel.engine.execute_unit import ExecuteUnit 3 | from quantdigger.kernel.indicators.common import MA, BOLL 4 | from quantdigger.kernel.engine.strategy import TradingStrategy 5 | from quantdigger.util import pcontract, stock 6 | from quantdigger.digger import deals 7 | import plotting 8 | #from quantdigger.kernel.engine.series import NumberSeries 9 | 10 | #def average(series, n): 11 | #""" 一个可选的平均线函数 """ 12 | ### @todo plot element 13 | #sum_ = 0 14 | #for i in range(0, n): 15 | #sum_ += series[i] 16 | #return sum_ / n 17 | 18 | 19 | 20 | class DemoStrategy(TradingStrategy): 21 | """ 策略实例 """ 22 | def __init__(self, exe): 23 | super(DemoStrategy, self).__init__(exe) 24 | 25 | self.ma20 = MA(self, self.close, 20,'ma20', 'b', '1') 26 | self.ma10 = MA(self, self.close, 10,'ma10', 'y', '1') 27 | self.b_upper, self.b_middler, self.b_lower = BOLL(self, self.close, 10,'boll10', 'y', '1') 28 | #self.ma2 = NumberSeries(self) 29 | 30 | def on_bar(self): 31 | """ 策略函数,对每根Bar运行一次。""" 32 | #self.ma2.update(average(self.open, 10)) 33 | if self.ma10[1] < self.ma20[1] and self.ma10 > self.ma20: 34 | self.buy('long', self.open, 1, contract = 'IF000.SHFE') 35 | elif self.position() > 0 and self.ma10[1] > self.ma20[1] and self.ma10 < self.ma20: 36 | self.sell('long', self.open, 1) 37 | 38 | # 夸品种数据引用 39 | print self.open_(1)[1], self.open 40 | #print self.position(), self.cash() 41 | #print self.datetime, self.b_upper, self.b_middler, self.b_lower 42 | 43 | if __name__ == '__main__': 44 | try: 45 | begin_dt, end_dt = None, None 46 | pcon = pcontract('IF000.SHFE', '10.Minute') 47 | #pcon = stock('600848') # 通过tushare下载股票数据 48 | simulator = ExecuteUnit([pcon, pcon], begin_dt, end_dt) 49 | algo = DemoStrategy(simulator) 50 | #algo1 = DemoStrategy(simulator) 51 | #algo2 = DemoStrategy(simulator) 52 | simulator.run() 53 | 54 | #for deal in algo.blotter.deal_positions: 55 | ## code... 56 | #print("----------------") 57 | #print("开仓时间: %s;成交价格: %f;买卖方向: %s;成交量: %d;") % \ 58 | #(deal.open_datetime, deal.open_price, Direction.type_to_str(deal.direction), deal.quantity) 59 | #print("平仓时间: %s;成交价格: %f;买卖方向: %s;成交量: %d;盈亏: %f;") % \ 60 | #(deal.close_datetime, deal.close_price, Direction.type_to_str(deal.direction), deal.quantity, deal.profit()) 61 | 62 | # 显示回测结果 63 | a = {} 64 | b = [] 65 | try: 66 | for trans in algo.blotter.transactions: 67 | deals.update_positions(a, b, trans); 68 | except Exception, e: 69 | print e 70 | plotting.plot_result(simulator.data[pcon], 71 | algo._indicators, 72 | b, 73 | algo.blotter) 74 | 75 | #print algo.blotter.pp 76 | #print sum(algo.blotter.pp) 77 | #plotting.plot_result(simulator.data[pcon], 78 | #algo2._indicators, 79 | #algo2.blotter.deal_positions, 80 | #algo2.blotter) 81 | 82 | except Exception, e: 83 | print e 84 | 85 | -------------------------------------------------------------------------------- /quantdigger/demo/mplot_demo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | #import os, sys 3 | #sys.path.append(os.path.join('..', '..')) 4 | import matplotlib 5 | matplotlib.use('TkAgg') 6 | import matplotlib.pyplot as plt 7 | from quantdigger.widgets.mplotwidgets import widgets, mplots 8 | from quantdigger.kernel.indicators.common import MA, RSI, Volume 9 | 10 | 11 | from quantdigger.kernel.datasource.data import get_stock_signal_data 12 | price_data, entry_x, entry_y, exit_x, exit_y, colors = get_stock_signal_data() 13 | 14 | 15 | #import matplotlib.font_manager as font_manager 16 | fig = plt.figure() 17 | frame = widgets.MultiWidgets(fig, price_data, 50, 4,3,1) 18 | ax_candles, ax_rsi, ax_volume = frame 19 | 20 | # 添加k线和交易信号。 21 | kwindow = widgets.CandleWindow("kwindow", price_data, 100, 50) 22 | candle_widget = frame.add_widget(0, kwindow, True) 23 | signal = mplots.TradingSignal(None, zip(zip(entry_x,entry_y),zip(exit_x,exit_y)), c=colors, lw=2) 24 | frame.add_indicator(0, signal) 25 | 26 | # 添加指标 27 | ma = frame.add_indicator(0, MA(None, price_data.close, 20, 'MA20', 'y', 2)) 28 | frame.add_indicator(0, MA(None, price_data.close, 30, 'MA30', 'b', 2)) 29 | frame.add_indicator(1, RSI(None, price_data.close, 14, name='RSI', fillcolor='b')) 30 | frame.add_indicator(2, Volume(None, price_data.open, price_data.close, price_data.vol)) 31 | frame.draw_widgets() 32 | 33 | # legend 34 | #props = font_manager.FontProperties(size=10) 35 | #leg = ax_candles.legend(loc='center left', shadow=True, fancybox=True, prop=props) 36 | #leg.get_frame().set_alpha(0.5) 37 | 38 | 39 | #ax2t.set_yticks([]) 40 | 41 | # at most 5 ticks, pruning the upper and lower so they don't overlap 42 | # with other ticks 43 | ax_volume.yaxis.set_major_locator(widgets.MyLocator(5, prune='both')) 44 | 45 | # sharex 所有所有的窗口都移动 46 | #frame.slider.add_observer(frame.rangew) 47 | #ma('hhh') 48 | 49 | plt.show() 50 | -------------------------------------------------------------------------------- /quantdigger/demo/plot_ma.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | import matplotlib 3 | matplotlib.use('TkAgg') 4 | import matplotlib.pyplot as plt 5 | from quantdigger.kernel.indicators.common import MA 6 | from quantdigger.kernel.datasource.data import csv2frame 7 | 8 | # 创建画布 9 | fig, ax = plt.subplots() 10 | # 加载数据 11 | price_data = csv2frame("IF000.SHFE-10.Minute.csv") 12 | # 创建平均线 13 | ma10 = MA(None, price_data.close, 10, 'MA10', 'y', 2) 14 | ma20 = MA(None, price_data.close, 60, 'MA10', 'b', 2) 15 | # 绘制指标 16 | ma10.plot(ax) 17 | ma20.plot(ax) 18 | plt.show() 19 | -------------------------------------------------------------------------------- /quantdigger/demo/plotting.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | import matplotlib 3 | matplotlib.use('TkAgg') 4 | import matplotlib.pyplot as plt 5 | from quantdigger.widgets.mplotwidgets import widgets, mplots 6 | from quantdigger.digger import finance 7 | 8 | def plot_result(price_data, indicators, signals, 9 | blotter): 10 | """ 11 | 显示回测结果。 12 | """ 13 | try: 14 | curve = finance.create_equity_curve_dataframe(blotter.all_holdings) 15 | print finance.output_summary_stats(curve) 16 | 17 | fig = plt.figure() 18 | frame = widgets.MultiWidgets(fig, 19 | price_data, 20 | 50 # 窗口显示k线数量。 21 | ) 22 | 23 | # 添加k线 24 | kwindow = widgets.CandleWindow("kwindow", price_data, 100, 50) 25 | frame.add_widget(0, kwindow, True) 26 | # 交易信号。 27 | signal = mplots.TradingSignalPos(None, price_data, signals, lw=2) 28 | frame.add_indicator(0, signal) 29 | 30 | # 添加指标 31 | k_axes, = frame 32 | for indic in indicators: 33 | indic.plot(k_axes) 34 | frame.draw_widgets() 35 | 36 | fig2 = plt.figure() 37 | ax = fig2.add_axes((0.1, 0.1, 0.9, 0.9)) 38 | ax.plot(curve.equity) 39 | plt.show() 40 | 41 | except Exception, e: 42 | print(e) 43 | -------------------------------------------------------------------------------- /quantdigger/demo/pyquant.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf8 -*- 3 | import os, sys 4 | sys.path.append(os.path.join('.')) 5 | from IPython.qt.console.rich_ipython_widget import RichIPythonWidget 6 | from IPython.qt.inprocess import QtInProcessKernelManager 7 | from PyQt4 import QtGui, QtCore 8 | from plugin.qtwidgets.techwidget import TechWidget 9 | from quantdigger.kernel.datasource import data 10 | from plugin.mplotwidgets import widgets 11 | from quantdigger.kernel.indicators import sys_indicator as indicator 12 | price_data, entry_x, entry_y, exit_x, exit_y, colors = data.get_stock_signal_data() 13 | 14 | class EmbedIPython(RichIPythonWidget): 15 | 16 | def __init__(self, **kwarg): 17 | super(RichIPythonWidget, self).__init__() 18 | self.kernel_manager = QtInProcessKernelManager() 19 | self.kernel_manager.start_kernel() 20 | self.kernel = self.kernel_manager.kernel 21 | self.kernel.gui = 'qt4' 22 | self.kernel.shell.push(kwarg) 23 | self.kernel_client = self.kernel_manager.client() 24 | self.kernel_client.start_channels() 25 | 26 | 27 | class MainWindow(QtGui.QMainWindow): 28 | 29 | def __init__(self, parent=None): 30 | super(MainWindow, self).__init__(parent) 31 | 32 | self.createToolBox() 33 | self.textEdit = QtGui.QTextEdit() 34 | 35 | self.center_widget = self.demo_plot() 36 | #self.center_widget.subplots_adjust(0.05, 0.05, 1, 1) 37 | 38 | self.createActions() 39 | self.createMenus() 40 | self.createToolBars() 41 | self.createStatusBar() 42 | self.createDockWindows() 43 | self.setWindowTitle("QuantDigger") 44 | self.setUnifiedTitleAndToolBarOnMac(True) 45 | self.setCentralWidget(self.center_widget) 46 | self.center_widget.setFocus(True) 47 | 48 | def about(self): 49 | QtGui.QMessageBox.about(self, "About Dock Widgets", 50 | "The Dock Widgets example demonstrates how to use " 51 | "Qt's dock widgets. You can enter your own text, click a " 52 | "customer to add a customer name and address, and click " 53 | "standard paragraphs to add them.") 54 | 55 | def createActions(self): 56 | #self.saveAct = QtGui.QAction(QtGui.QIcon(':/images/save.png'), 57 | #"&Save...", self, shortcut=QtGui.QKeySequence.Save, 58 | #statusTip="Save the current form letter", 59 | #triggered=self.save) 60 | 61 | 62 | self.quitAct = QtGui.QAction("&Quit", self, shortcut="Ctrl+Q", 63 | statusTip="Quit the application", triggered=self.close) 64 | 65 | self.aboutAct = QtGui.QAction("&About", self, 66 | statusTip="Show the application's About box", 67 | triggered=self.about) 68 | 69 | self.aboutQtAct = QtGui.QAction("About &Qt", self, 70 | statusTip="Show the Qt library's About box", 71 | triggered=QtGui.qApp.aboutQt) 72 | 73 | def createMenus(self): 74 | self.fileMenu = self.menuBar().addMenu("&File") 75 | self.fileMenu.addSeparator() 76 | self.fileMenu.addAction(self.quitAct) 77 | self.viewMenu = self.menuBar().addMenu("&View") 78 | 79 | self.menuBar().addSeparator() 80 | 81 | self.helpMenu = self.menuBar().addMenu("&Help") 82 | self.helpMenu.addAction(self.aboutAct) 83 | self.helpMenu.addAction(self.aboutQtAct) 84 | 85 | def createToolBars(self): 86 | self.fileToolBar = self.addToolBar("File") 87 | self.fileToolBar.addAction(self.aboutQtAct) 88 | 89 | self.editToolBar = self.addToolBar("Edit") 90 | self.fileToolBar.addAction(self.quitAct) 91 | 92 | def createStatusBar(self): 93 | self.statusBar().showMessage("Ready") 94 | 95 | def createDockWindows(self): 96 | # Ipython终端栏 97 | self.console = EmbedIPython(testing=123) 98 | self.console.kernel.shell.run_cell('%pylab qt') 99 | dock = QtGui.QDockWidget("Ipython Console", self) 100 | dock.setWidget(self.console) 101 | dock.setAllowedAreas(QtCore.Qt.BottomDockWidgetArea | QtCore.Qt.TopDockWidgetArea) 102 | self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, dock) 103 | self.viewMenu.addAction(dock.toggleViewAction()) 104 | # ToolBox工具栏 105 | dock = QtGui.QDockWidget("Customers", self) 106 | dock.setAllowedAreas(QtCore.Qt.LeftDockWidgetArea | QtCore.Qt.RightDockWidgetArea) 107 | dock.setWidget(self.toolBox) 108 | self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, dock) 109 | self.viewMenu.addAction(dock.toggleViewAction()) 110 | #self.customerList.currentTextChanged.connect(self.insertCustomer) 111 | #self.paragraphsList.currentTextChanged.connect(self.addParagraph) 112 | 113 | 114 | def demo_plot(self): 115 | frame = TechWidget(self, 80, 4, 3, 1) 116 | ax_candles, ax_rsi, ax_volume = frame 117 | 118 | # 把窗口传给techplot, 连接信号 119 | # 事件处理 |||| 绘图,数据的分离。 120 | # twins窗口。 121 | # rangew 122 | # 事件从techplot传到PYQT 123 | 124 | try: 125 | kwindow = widgets.CandleWindow("kwindow", price_data, 100, 50) 126 | frame.add_widget(0, kwindow, True) 127 | signal = indicator.TradingSignal(zip(zip(entry_x,entry_y),zip(exit_x,exit_y)), c=colors, lw=2) 128 | frame.add_indicator(0, signal) 129 | 130 | # 指标窗口 131 | frame.add_indicator(0, indicator.MA(price_data.close, 20, 'MA20', 'y', 2)) 132 | frame.add_indicator(0, indicator.MA(price_data.close, 30, 'MA30', 'b', 2)) 133 | frame.add_indicator(1, indicator.RSI(price_data.close, 14, name='RSI', fillcolor='b')) 134 | frame.add_indicator(2, indicator.Volume(price_data.open, price_data.close, price_data.vol)) 135 | frame.draw_window() 136 | except Exception, e: 137 | print "----------------------" 138 | print e 139 | 140 | return frame 141 | 142 | 143 | 144 | 145 | 146 | def createToolBox(self): 147 | self.buttonGroup = QtGui.QButtonGroup() 148 | self.buttonGroup.setExclusive(False) 149 | #self.buttonGroup.buttonClicked[int].connect(self.buttonGroupClicked) 150 | 151 | layout = QtGui.QGridLayout() 152 | #layout.addWidget(self.createCellWidget("Conditional", DiagramItem.Conditional), 0, 0) 153 | #layout.addWidget(self.createCellWidget("Process", DiagramItem.Step), 0, 1) 154 | #layout.addWidget(self.createCellWidget("Input/Output", DiagramItem.Io), 1, 0) 155 | 156 | textButton = QtGui.QToolButton() 157 | textButton.setCheckable(True) 158 | textButton.setIcon(QtGui.QIcon(QtGui.QPixmap(':/images/textpointer.png') 159 | .scaled(30, 30))) 160 | textButton.setIconSize(QtCore.QSize(50, 50)) 161 | 162 | textLayout = QtGui.QGridLayout() 163 | textLayout.addWidget(textButton, 0, 0, QtCore.Qt.AlignHCenter) 164 | textLayout.addWidget(QtGui.QLabel("Text"), 1, 0, 165 | QtCore.Qt.AlignCenter) 166 | textWidget = QtGui.QWidget() 167 | textWidget.setLayout(textLayout) 168 | layout.addWidget(textWidget, 1, 1) 169 | 170 | layout.setRowStretch(3, 10) 171 | layout.setColumnStretch(2, 10) 172 | 173 | itemWidget = QtGui.QWidget() 174 | itemWidget.setLayout(layout) 175 | 176 | self.backgroundButtonGroup = QtGui.QButtonGroup() 177 | #self.backgroundButtonGroup.buttonClicked.connect(self.backgroundButtonGroupClicked) 178 | 179 | backgroundLayout = QtGui.QGridLayout() 180 | backgroundLayout.addWidget(self.createBackgroundCellWidget("Blue Grid", 181 | ':/images/background1.png'), 0, 0) 182 | backgroundLayout.addWidget(self.createBackgroundCellWidget("White Grid", 183 | ':/images/background2.png'), 0, 1) 184 | backgroundLayout.addWidget(self.createBackgroundCellWidget("Gray Grid", 185 | ':/images/background3.png'), 1, 0) 186 | backgroundLayout.addWidget(self.createBackgroundCellWidget("No Grid", 187 | ':/images/background4.png'), 1, 1) 188 | 189 | backgroundLayout.setRowStretch(2, 10) 190 | backgroundLayout.setColumnStretch(2, 10) 191 | 192 | backgroundWidget = QtGui.QWidget() 193 | backgroundWidget.setLayout(backgroundLayout) 194 | 195 | self.toolBox = QtGui.QToolBox() 196 | self.toolBox.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Ignored)) 197 | self.toolBox.setMinimumWidth(itemWidget.sizeHint().width()) 198 | self.toolBox.addItem(itemWidget, "Basic Flowchart Shapes") 199 | self.toolBox.addItem(backgroundWidget, "Backgrounds") 200 | 201 | def createBackgroundCellWidget(self, text, image): 202 | button = QtGui.QToolButton() 203 | button.setText(text) 204 | button.setIcon(QtGui.QIcon(image)) 205 | button.setIconSize(QtCore.QSize(50, 50)) 206 | button.setCheckable(True) 207 | self.backgroundButtonGroup.addButton(button) 208 | 209 | layout = QtGui.QGridLayout() 210 | layout.addWidget(button, 0, 0, QtCore.Qt.AlignHCenter) 211 | layout.addWidget(QtGui.QLabel(text), 1, 0, QtCore.Qt.AlignCenter) 212 | 213 | widget = QtGui.QWidget() 214 | widget.setLayout(layout) 215 | return widget 216 | 217 | def keyPressEvent(self, event): 218 | key = event.key() 219 | print(key) 220 | if key == QtCore.Qt.Key_Left: 221 | print('Left Arrow Pressed') 222 | 223 | 224 | 225 | if __name__ == '__main__': 226 | import sys 227 | app = QtGui.QApplication(sys.argv) 228 | main = MainWindow() 229 | main.show() 230 | sys.exit(app.exec_()) 231 | ## 执行终端语句 232 | ##self.console.execute("print('a[\\\'text\\\'] = \"'+ a['text'] +'\"')") 233 | -------------------------------------------------------------------------------- /quantdigger/digger/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/quantdigger/digger/__init__.py -------------------------------------------------------------------------------- /quantdigger/digger/analyze.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/quantdigger/digger/analyze.py -------------------------------------------------------------------------------- /quantdigger/digger/deals.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | from quantdigger.kernel.datastruct import TradeSide, Direction 3 | class PositionsDetail(object): 4 | """ 当前相同合约持仓集合(可能不同时间段下单)。 5 | 6 | :ivar cost: 持仓成本。 7 | :ivar total: 持仓总数。 8 | :ivar positions: 持仓集合。 9 | :vartype positions: list 10 | """ 11 | def __init__(self): 12 | self.total = 0 13 | self.positions = [] 14 | self.cost = 0 15 | 16 | 17 | 18 | class DealPosition(object): 19 | """ 开仓,平仓对 20 | 21 | :ivar open: 开仓价 22 | :vartype open: float 23 | :ivar close: 平仓价 24 | :vartype close: float 25 | """ 26 | def __init__(self, buy_trans, sell_trans): 27 | self.open = buy_trans 28 | self.close = sell_trans 29 | 30 | def profit(self): 31 | """ 盈亏额 """ 32 | direction = self.open.direction 33 | if direction == Direction.LONG: 34 | return (self.close.price - self.open.price) * self.open.quantity 35 | else: 36 | return (self.open.price - self.close.price) * self.open.quantity 37 | 38 | @property 39 | def quantity(self): 40 | """ 成交量 """ 41 | return self.open.quantity 42 | 43 | @property 44 | def open_datetime(self): 45 | """ 开仓时间 """ 46 | return self.open.datetime 47 | 48 | @property 49 | def open_price(self): 50 | """ 开仓价格 """ 51 | return self.open.price 52 | 53 | @property 54 | def close_datetime(self): 55 | """ 平仓时间 """ 56 | return self.close.datetime 57 | 58 | @property 59 | def close_price(self): 60 | """ 平仓价格 """ 61 | return self.close.price 62 | 63 | @property 64 | def direction(self): 65 | """ 空头还是多头 """ 66 | return self.open.direction 67 | 68 | 69 | def update_positions(current_positions, deal_positions, trans): 70 | ## @todo 把复杂统计单独出来。 71 | """ 更新持仓 """ 72 | p = current_positions.setdefault(trans.contract, PositionsDetail()) 73 | if trans.side == TradeSide.KAI: 74 | # 开仓 75 | p.positions.append(trans) 76 | p.total += trans.quantity 77 | elif trans.side == TradeSide.PING: 78 | # 平仓 79 | p.total -= trans.quantity 80 | left_vol = trans.quantity 81 | last_index = -1 82 | for position in reversed(p.positions): 83 | deal_positions.append(DealPosition(position, trans)) 84 | if position.quantity < left_vol: 85 | # 还需从之前的仓位中平。 86 | left_vol -= position.quantity 87 | last_index -= 1 88 | 89 | elif position.quantity == left_vol: 90 | left_vol -= position.quantity 91 | last_index -= 1 92 | break 93 | 94 | else: 95 | position.quantity -= left_vol 96 | left_vol = 0 97 | break 98 | 99 | p.positions = p.positions[0 : last_index] 100 | assert(left_vol == 0) 101 | -------------------------------------------------------------------------------- /quantdigger/digger/finance.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | import pandas as pd 3 | import numpy as np 4 | 5 | def create_sharpe_ratio(returns, periods=252): 6 | """ 7 | Create the Sharpe ratio for the strategy, based on a 8 | benchmark of zero (i.e. no risk-free rate information). 9 | 10 | Parameters: 11 | returns - A pandas Series representing period percentage returns. 12 | periods - Daily (252), Hourly (252*6.5), Minutely(252*6.5*60) etc. 13 | """ 14 | return np.sqrt(periods) * (np.mean(returns)) / np.std(returns) 15 | 16 | 17 | def create_drawdowns(equity_curve): 18 | """ 19 | Calculate the largest peak-to-trough drawdown of the PnL curve 20 | as well as the duration of the drawdown. Requires that the 21 | pnl_returns is a pandas Series. 22 | 23 | Parameters: 24 | pnl - A pandas Series representing period percentage returns. 25 | 26 | Returns: 27 | drawdown, duration - Highest peak-to-trough drawdown and duration. 28 | """ 29 | 30 | # Calculate the cumulative returns curve 31 | # and set up the High Water Mark 32 | # Then create the drawdown and duration series 33 | hwm = [0] 34 | eq_idx = equity_curve.index 35 | drawdown = pd.Series(index = eq_idx) 36 | duration = pd.Series(index = eq_idx) 37 | 38 | # Loop over the index range 39 | for t in range(1, len(eq_idx)): 40 | cur_hwm = max(hwm[t-1], equity_curve[t]) 41 | hwm.append(cur_hwm) 42 | drawdown[t]= hwm[t] - equity_curve[t] 43 | duration[t]= 0 if drawdown[t] == 0 else duration[t-1] + 1 44 | return drawdown.max(), duration.max() 45 | 46 | 47 | def create_equity_curve_dataframe(all_holdings): 48 | """ 49 | 创建资金曲线对象。 50 | """ 51 | curve = pd.DataFrame(all_holdings) 52 | curve.set_index('datetime', inplace=True) 53 | curve['returns'] = curve['equity'].pct_change() 54 | curve['equity_curve'] = (1.0+curve['returns']).cumprod() 55 | return curve 56 | 57 | def output_summary_stats(curve): 58 | """ 59 | 统计夏普率, 回测等信息。 60 | """ 61 | total_return = curve['equity_curve'][-1] 62 | returns = curve['returns'] 63 | pnl = curve['equity_curve'] 64 | 65 | sharpe_ratio = create_sharpe_ratio(returns) 66 | max_dd, dd_duration = create_drawdowns(pnl) 67 | 68 | stats = [("Total Return", "%0.2f%%" % ((total_return - 1.0) * 100.0)), 69 | ("Sharpe Ratio", "%0.2f" % sharpe_ratio), 70 | ("Max Drawdown", "%0.2f%%" % (max_dd * 100.0)), 71 | ("Drawdown Duration", "%d" % dd_duration)] 72 | return stats 73 | -------------------------------------------------------------------------------- /quantdigger/errors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | class QuantDiggerError(Exception): 4 | msg = None 5 | 6 | def __init__(self, *args, **kwargs): 7 | self.args = args 8 | self.kwargs = kwargs 9 | self.message = str(self) 10 | 11 | 12 | def __str__(self): 13 | msg = self.msg.format(**self.kwargs) 14 | return msg 15 | 16 | __unicode__ = __str__ 17 | __repr__ = __str__ 18 | 19 | 20 | class DataFormatError(QuantDiggerError): 21 | """ 22 | """ 23 | msg = "错误的数据格式!" 24 | 25 | 26 | class FileDoesNotExist(QuantDiggerError): 27 | """ 28 | 当本地文件不存在的时候触发。 29 | """ 30 | msg = "不存在文件:{file}" 31 | 32 | 33 | class PeriodTypeError(QuantDiggerError): 34 | msg = "不存在该周期!" 35 | 36 | 37 | class DataAlignError(QuantDiggerError): 38 | msg = "数据没有对齐!" 39 | 40 | class SeriesIndexError(QuantDiggerError): 41 | msg = "序列变量索引越界!" 42 | 43 | class BreakConstError(QuantDiggerError): 44 | msg = "不能对常量赋值!" 45 | 46 | 47 | 48 | class WrongDataForTransform(QuantDiggerError): 49 | """ 50 | Raised whenever a rolling transform is called on an event that 51 | does not have the necessary properties. 52 | """ 53 | msg = "{transform} requires {fields}. Event cannot be processed." 54 | 55 | 56 | class UnsupportedSlippageModel(QuantDiggerError): 57 | """ 58 | Raised if a user script calls the override_slippage magic 59 | with a slipage object that isn't a VolumeShareSlippage or 60 | FixedSlipapge 61 | """ 62 | msg = """ 63 | You attempted to override slippage with an unsupported class. \ 64 | Please use VolumeShareSlippage or FixedSlippage. 65 | """.strip() 66 | 67 | 68 | class OverrideSlippagePostInit(QuantDiggerError): 69 | # Raised if a users script calls override_slippage magic 70 | # after the initialize method has returned. 71 | msg = """ 72 | You attempted to override slippage outside of `initialize`. \ 73 | You may only call override_slippage in your initialize method. 74 | """.strip() 75 | 76 | 77 | class RegisterTradingControlPostInit(QuantDiggerError): 78 | # Raised if a user's script register's a trading control after initialize 79 | # has been run. 80 | msg = """ 81 | You attempted to set a trading control outside of `initialize`. \ 82 | Trading controls may only be set in your initialize method. 83 | """.strip() 84 | 85 | 86 | class UnsupportedCommissionModel(QuantDiggerError): 87 | """ 88 | Raised if a user script calls the override_commission magic 89 | with a commission object that isn't a PerShare, PerTrade or 90 | PerDollar commission 91 | """ 92 | msg = """ 93 | You attempted to override commission with an unsupported class. \ 94 | Please use PerShare or PerTrade. 95 | """.strip() 96 | 97 | 98 | class OverrideCommissionPostInit(QuantDiggerError): 99 | """ 100 | Raised if a users script calls override_commission magic 101 | after the initialize method has returned. 102 | """ 103 | msg = """ 104 | You attempted to override commission outside of `initialize`. \ 105 | You may only call override_commission in your initialize method. 106 | """.strip() 107 | 108 | 109 | class TransactionWithNoVolume(QuantDiggerError): 110 | """ 111 | Raised if a transact call returns a transaction with zero volume. 112 | """ 113 | msg = """ 114 | Transaction {txn} has a volume of zero. 115 | """.strip() 116 | 117 | 118 | class TransactionWithWrongDirection(QuantDiggerError): 119 | """ 120 | Raised if a transact call returns a transaction with a direction that 121 | does not match the order. 122 | """ 123 | msg = """ 124 | Transaction {txn} not in same direction as corresponding order {order}. 125 | """.strip() 126 | 127 | 128 | class TransactionWithNoAmount(QuantDiggerError): 129 | """ 130 | Raised if a transact call returns a transaction with zero amount. 131 | """ 132 | msg = """ 133 | Transaction {txn} has an amount of zero. 134 | """.strip() 135 | 136 | 137 | class TransactionVolumeExceedsOrder(QuantDiggerError): 138 | """ 139 | Raised if a transact call returns a transaction with a volume greater than 140 | the corresponding order. 141 | """ 142 | msg = """ 143 | Transaction volume of {txn} exceeds the order volume of {order}. 144 | """.strip() 145 | 146 | 147 | class UnsupportedOrderParameters(QuantDiggerError): 148 | """ 149 | Raised if a set of mutually exclusive parameters are passed to an order 150 | call. 151 | """ 152 | msg = "{msg}" 153 | 154 | 155 | class BadOrderParameters(QuantDiggerError): 156 | """ 157 | Raised if any impossible parameters (nan, negative limit/stop) 158 | are passed to an order call. 159 | """ 160 | msg = "{msg}" 161 | 162 | 163 | class OrderDuringInitialize(QuantDiggerError): 164 | """ 165 | Raised if order is called during initialize() 166 | """ 167 | msg = "{msg}" 168 | 169 | 170 | class TradingControlViolation(QuantDiggerError): 171 | """ 172 | Raised if an order would violate a constraint set by a TradingControl. 173 | """ 174 | msg = """ 175 | Order for {amount} shares of {sid} violates trading constraint {constraint}. 176 | """.strip() 177 | 178 | 179 | class IncompatibleHistoryFrequency(QuantDiggerError): 180 | """ 181 | Raised when a frequency is given to history which is not supported. 182 | At least, not yet. 183 | """ 184 | msg = """ 185 | Requested history at frequency '{frequency}' cannot be created with data 186 | at frequency '{data_frequency}'. 187 | """.strip() 188 | -------------------------------------------------------------------------------- /quantdigger/images/new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/quantdigger/images/new.png -------------------------------------------------------------------------------- /quantdigger/images/print.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/quantdigger/images/print.png -------------------------------------------------------------------------------- /quantdigger/images/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/quantdigger/images/save.png -------------------------------------------------------------------------------- /quantdigger/images/undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/quantdigger/images/undo.png -------------------------------------------------------------------------------- /quantdigger/kernel/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/quantdigger/kernel/__init__.py -------------------------------------------------------------------------------- /quantdigger/kernel/datasource/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/quantdigger/kernel/datasource/__init__.py -------------------------------------------------------------------------------- /quantdigger/kernel/datasource/data/trace/_djtrend2_IF000.csv: -------------------------------------------------------------------------------- 1 | entry_datetime,entry_price,exit_datetime,exit_price 2 | 2014-01-22 10:15:00,2234,2014-01-27 09:45:00,2236 3 | 2014-02-11 13:30:00,2282,2014-02-18 14:15:00,2280 4 | 2014-03-24 09:30:00,2140,2014-04-18 09:30:00,2198 5 | 2014-05-12 11:15:00,2164,2014-05-19 09:45:00,2102 6 | 2014-05-26 10:00:00,2154,2014-05-28 09:30:00,2130 7 | 2014-06-30 10:30:00,2158,2014-07-09 14:15:00,2140 8 | 2014-07-22 13:45:00,2186,2014-07-23 14:15:00,2182 9 | 2014-07-24 10:30:00,2214,2014-10-27 09:30:00,2386 10 | 2014-10-29 13:15:00,2472,2014-11-07 14:30:00,2496 11 | -------------------------------------------------------------------------------- /quantdigger/kernel/datasource/data/trace/_djtrend2_IF000_.csv: -------------------------------------------------------------------------------- 1 | entry_datetime,entry_price,exit_datetime,exit_price 2 | 2014-01-06 09:45:00,2262,2014-01-08 09:45:00,2262 3 | 2014-01-30 15:00:00,2210,2014-02-10 10:15:00,2260 4 | 2014-02-21 13:45:00,2264,2014-03-03 11:15:00,2182 5 | 2014-03-06 10:45:00,2130,2014-03-07 10:00:00,2174 6 | 2014-03-10 09:45:00,2102,2014-03-13 09:30:00,2094 7 | 2014-03-21 10:15:00,2054,2014-03-21 13:15:00,2088 8 | 2014-04-22 13:00:00,2174,2014-04-23 09:45:00,2186 9 | 2014-04-28 10:45:00,2138,2014-04-29 14:15:00,2154 10 | 2014-05-19 10:30:00,2094,2014-05-21 10:00:00,2104 11 | 2014-06-19 14:15:00,2132,2014-06-30 10:15:00,2164 12 | 2014-07-09 15:00:00,2136,2014-07-14 13:15:00,2158 13 | 2014-10-27 09:45:00,2380,2014-10-29 09:30:00,2436 14 | -------------------------------------------------------------------------------- /quantdigger/kernel/datasource/data_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import time 3 | from pymongo import MongoClient 4 | import pandas as pd 5 | 6 | class Min(object): 7 | count = 0 8 | t = 0 # unix time with ms 9 | ct ='0' # contract 10 | o = 0 # open price 11 | h =0 # high price 12 | c =0 # close price 13 | l= 999999999 # low price 14 | v = 0 # vol 15 | ticks = {} 16 | def clear(self): 17 | self.count =0 18 | self.t = 0 19 | self.c =0 20 | self.o =0 21 | self.v = 0 22 | self.l = 999999999 23 | self.h = 0 24 | self.ct = '0' 25 | self.ticks = {} 26 | 27 | def addToTicks(self, simpleTick): 28 | length = len(self.ticks) 29 | self.ticks[str(length)] = simpleTick.__dict__ 30 | 31 | def update(self, tick): 32 | if self.count == 0 : 33 | self.t = tick.t 34 | self.o = tick.o 35 | self.ct = tick.ct 36 | self.c = tick.o 37 | self.h = (self.h if self.h > tick.o else tick.o) 38 | self.l = (self.l if self.l < tick.o else tick.o) 39 | self.v += tick.v 40 | self.count += 1 41 | self.addToTicks(SimpleTick(tick)) 42 | 43 | def toTicksDict(self): 44 | d = {'t' : self.t , 'ct' : self.ct} 45 | d.update(self.ticks) 46 | return d 47 | 48 | def save(self, client, db): 49 | minDoc = dict(t=self.t, ct=self.ct, o=self.o, h = self.h, l = self.l, c = self.c, v = self.v); 50 | 51 | db['mins'].insert_one(minDoc) 52 | 53 | d = self.toTicksDict() 54 | db['ticks'].insert_one(d) 55 | print 'save min...' 56 | 57 | class HalfHour(object): 58 | count = 0 59 | t = 0 # unix time with ms 60 | ct ='0' # contract 61 | o = 0 # open price 62 | h =0 # high price 63 | c =0 # close price 64 | l= 999999999 # low price 65 | v = 0 # vol 66 | 67 | def clear(self): 68 | self.count = 0 69 | self.t = 0 # unix time with ms 70 | self.ct ='0' # contract 71 | self.o = 0 # open price 72 | self.h =0 # high price 73 | self.c =0 # close price 74 | self.l= 999999999 # low price 75 | self.v = 0 # vol 76 | 77 | def update(self, min) : 78 | if self.count == 0 : 79 | self.t = min.t 80 | self.ct = min.ct 81 | self.o = min.o 82 | self.c = min.c 83 | self.h = (self.h if self.h > min.h else min.h) 84 | self.l = (self.l if self.l < min.l else min.l) 85 | self.v += min.v 86 | self.count += 1 87 | 88 | def save(self, client, db): 89 | d = {} 90 | self.__dict__['count'] 91 | d.update(self.__dict__) 92 | del d['count'] 93 | db['halfhours'].insert_one(d) 94 | 95 | class Tick(object): 96 | t = 0 # unix time with ms 97 | ct ='0' # contract 98 | o = 0 # open price 99 | v = 0 # vol 100 | 101 | class SimpleTick(object): 102 | def __init__(self, tick): 103 | self.o = tick.o 104 | self.v = tick.v 105 | 106 | def isSameMin(ta, tb): 107 | return (abs(ta-tb) < 60000) and (int(ta / 60000) == int(tb / 60000)) 108 | 109 | def isSameHalfHour(ta, tb): 110 | return (abs(ta-tb) < 1800000) and (int(ta / 1800000) == int(tb / 1800000)) 111 | 112 | # filepath: data file 113 | def import_data(filepath): 114 | d = pd.read_csv(filepath) 115 | client = MongoClient() 116 | db = client['test'] 117 | minData = Min() 118 | halfHourData = HalfHour() 119 | 120 | for index in range(len(d)) : 121 | print index 122 | tick = Tick() 123 | tick.ct = d.iloc[index]['code'] 124 | tick.t = time.mktime(time.strptime(d.iloc[index]['datetime'], '%Y-%m-%d %H:%M:%S')) * 1000 125 | tick.o = d.iloc[index]['price'] 126 | tick.v = d.iloc[index]['vol'] 127 | 128 | tag = (minData.count == 0 or isSameMin(minData.t, tick.t)) 129 | 130 | # if isSameMin(minData.t, tick.t) == False: 131 | # print 'ta: ', str(minData.t), 'tb: ', str(tick.t) 132 | if tag: 133 | minData.update(tick) 134 | # print 'update min data, count= ' , minData.count 135 | # print 'isSameMin= ' , isSameMin(minData.t, tick.t) , 'ta= ' , minData.t/1000, 'tb= ' , tick.t/1000 136 | else : 137 | minData.save(client, db) 138 | if halfHourData.count == 0 or isSameHalfHour(halfHourData.t, minData.t): 139 | halfHourData.update(minData) 140 | else : 141 | halfHourData.save(client, db) 142 | halfHourData.clear() 143 | halfHourData.update(minData) 144 | minData.clear() 145 | minData.update(tick) 146 | 147 | # begin_time, end_time: unix time with ms 148 | def get_data(contract, kline, begin_time, end_time): 149 | client = MongoClient() 150 | db = client['test'] 151 | result = db[kline].find({'ct' : str(contract), 't' : {'$gte' : begin_time, '$lte' : end_time}}) 152 | rows = [] 153 | for res in result : 154 | rows.append(res) 155 | return rows 156 | 157 | # example for importing data 158 | filepath = 'if1508.csv' 159 | import_data(filepath); 160 | 161 | # example for reading data 162 | #rows = get_data('0', 'mins', 1340084340000, 1340094340000) 163 | 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /quantdigger/kernel/datasource/quote_cache.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/quantdigger/kernel/datasource/quote_cache.py -------------------------------------------------------------------------------- /quantdigger/kernel/engine/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/quantdigger/kernel/engine/__init__.py -------------------------------------------------------------------------------- /quantdigger/kernel/engine/api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | from quantdigger.kernel.engine.event import OrderEvent 3 | from abc import ABCMeta, abstractmethod 4 | 5 | class Trader(object): 6 | __metaclass__ = ABCMeta 7 | """ 交易类,包含了回调注册等高级封装。 """ 8 | 9 | def __init__(self, arg): 10 | pass 11 | 12 | @abstractmethod 13 | def connect(self): 14 | """ 连接器 """ 15 | pass 16 | 17 | #@abstractmethod 18 | #def register_handlers(self, handlers): 19 | #""" 注册回调函数 """ 20 | #pass 21 | 22 | @abstractmethod 23 | def query_contract(self, contract, sync=False): 24 | """ 合约查询 """ 25 | pass 26 | 27 | @abstractmethod 28 | def query_tick(self, contract, sync=False): 29 | """ 深度行情数据 """ 30 | pass 31 | 32 | @abstractmethod 33 | def query_captital(self, sync=False): 34 | """ 查询资金账户 """ 35 | pass 36 | 37 | @abstractmethod 38 | def query_position(self, sync=False): 39 | """ 查询投资者持仓""" 40 | pass 41 | 42 | @abstractmethod 43 | def order(self, order, sync=False): 44 | """ 下单请求 45 | 46 | :param Order order: 委托订单。 47 | """ 48 | pass 49 | 50 | @abstractmethod 51 | def cancel_order(self, orderid, sync=False): 52 | """ 撤单操作请求 """ 53 | pass 54 | 55 | def on_transaction(self, trans): 56 | """ 委托成交回调 """ 57 | pass 58 | 59 | def on_tick(self, tick): 60 | """ tick数据回调 """ 61 | pass 62 | 63 | def on_captial(self, tick): 64 | """ 资金查询回调 """ 65 | pass 66 | 67 | def on_position(self, tick): 68 | """ 持仓查询回调 """ 69 | pass 70 | 71 | 72 | class CtpTraderAPI(object): 73 | """ Ctp交易类 """ 74 | def __init__(self): 75 | pass 76 | 77 | def connect(self): 78 | """ 连接""" 79 | pass 80 | 81 | def query_contract(self, contract): 82 | """ 合约查询 """ 83 | pass 84 | 85 | def query_tick(self, contract): 86 | """ 深度行情数据 """ 87 | pass 88 | 89 | def query_captital(self): 90 | """ 查询资金账户 """ 91 | pass 92 | 93 | def query_position(self): 94 | """ 查询投资者持仓""" 95 | pass 96 | 97 | def order(self, order): 98 | """ 下单请求 """ 99 | pass 100 | 101 | def cancel_order(self, orderid): 102 | """ 撤单操作请求 """ 103 | pass 104 | 105 | def on_transaction(self, trans): 106 | """ 委托成交回调 """ 107 | pass 108 | 109 | def on_tick(self, tick): 110 | """ tick数据回调 """ 111 | pass 112 | 113 | def on_captial(self, tick): 114 | """ 资金查询回调 """ 115 | pass 116 | 117 | def on_position(self, tick): 118 | """ 持仓查询回调 """ 119 | pass 120 | 121 | 122 | class SimulateTraderAPI(Trader): 123 | """ 模拟交易下单接口 """ 124 | def __init__(self, blotter, events_pool): 125 | self._blotter = blotter 126 | self._events = events_pool 127 | 128 | def connect(self): 129 | """ 连接""" 130 | pass 131 | 132 | def query_contract(self, contract): 133 | """ 合约查询 """ 134 | pass 135 | 136 | def query_tick(self, contract): 137 | """ 深度行情数据 """ 138 | pass 139 | 140 | def query_captital(self): 141 | """ 查询资金账户 """ 142 | pass 143 | 144 | def query_position(self): 145 | """ 查询投资者持仓""" 146 | pass 147 | 148 | def order(self, order): 149 | """ 模拟下单 """ 150 | self._events.put(OrderEvent(order)) 151 | 152 | def cancel_order(self, orderid): 153 | """ 撤单操作请求 """ 154 | pass 155 | 156 | def on_transaction(self, trans): 157 | """ 模拟委托成交回调 """ 158 | self._blotter.update_fill(trans) 159 | 160 | def on_tick(self, tick): 161 | """ tick数据回调 """ 162 | pass 163 | 164 | def on_captial(self, tick): 165 | """ 资金查询回调 """ 166 | pass 167 | 168 | def on_position(self, tick): 169 | """ 持仓查询回调 """ 170 | pass 171 | -------------------------------------------------------------------------------- /quantdigger/kernel/engine/blotter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | from abc import ABCMeta, abstractmethod 3 | 4 | from quantdigger.kernel.engine.event import OrderEvent, Event 5 | from quantdigger.kernel.datastruct import Position, TradeSide, Direction, PriceType 6 | from quantdigger.util import engine_logger as logger 7 | from api import SimulateTraderAPI 8 | 9 | 10 | 11 | class Blotter(object): 12 | """ 13 | 订单管理。 14 | """ 15 | __metaclass__ = ABCMeta 16 | 17 | @abstractmethod 18 | def update_signal(self, event): 19 | """ 20 | 处理策略函数产生的下单事件。 21 | """ 22 | raise NotImplementedError("Should implement update_signal()") 23 | 24 | @abstractmethod 25 | def update_fill(self, event): 26 | """ 27 | 处理委托单成交事件。 28 | """ 29 | raise NotImplementedError("Should implement update_fill()") 30 | 31 | 32 | class SimpleBlotter(Blotter): 33 | """ 34 | 简单的订单管理系统,直接给 :class:`quantdigger.kernel.engine.exchange.Exchange` 35 | 对象发订单,没有风控。 36 | """ 37 | def __init__(self, timeseries, events_pool, initial_capital=5000.0): 38 | self._timeseries = timeseries 39 | self._open_orders = list() 40 | self._pre_settlement = 0 # 昨日结算价 41 | self._datetime = None # 当前时间 42 | self._init_captial = initial_capital 43 | self.api = SimulateTraderAPI(self, events_pool) # 模拟交易接口 44 | 45 | # 用于分析策略性能数据 46 | self.all_orders = [] 47 | self.initial_capital = initial_capital # 初始权益 48 | 49 | self.current_positions = {} # 当前持仓 dict of list, 包含细节。 50 | self.current_holdings = {} # 当前的资金 dict 51 | self.all_holdings = [] # 所有时间点上的资金 list of dict 52 | self.transactions = [] 53 | self.pp = [] 54 | 55 | def _init_state(self): 56 | self.current_holdings = { 57 | 'cash': self.initial_capital, 58 | 'commission': 0.0, 59 | 'history_profit': 0.0, 60 | 'equity': self.initial_capital 61 | } 62 | self.all_holdings = [{ 63 | 'datetime': self._start_date, 64 | 'cash': self.initial_capital, 65 | 'commission': 0.0, 66 | 'equity': self.initial_capital 67 | }] 68 | 69 | def update_bar(self, bars): 70 | """ 当前bar数据更新。 """ 71 | self._bars = bars 72 | 73 | def update_datetime(self, dt): 74 | """ 75 | 在新的价格数据来的时候触发。 76 | """ 77 | # 78 | if self._datetime == None: 79 | self._start_date = dt 80 | self._init_state() 81 | self._datetime = dt 82 | self._update_status(dt) 83 | 84 | def _update_status(self, dt): 85 | """ 更新历史持仓,当前权益。""" 86 | 87 | # 更新资金历史。 88 | ## @todo 由持仓历史推断资金历史。 89 | dh = { } 90 | dh['datetime'] = dt 91 | dh['commission'] = self.current_holdings['commission'] 92 | profit = 0 93 | margin = 0 94 | order_margin = 0; 95 | 96 | # 计算当前持仓历史盈亏。 97 | # 以close价格替代市场价格。 98 | is_stock = True # 默认是股票回测 99 | for contract, pos in self.current_positions.iteritems(): 100 | new_price = self._bars[contract].close 101 | profit += pos.profit(new_price) 102 | ## @todo 用昨日结算价计算保证金 103 | margin += pos.position_margin(new_price) 104 | if not contract.is_stock: 105 | is_stock = False # 106 | 107 | # 计算限价报单的保证金占用 108 | for order in self._open_orders: 109 | assert(order.price_type == PriceType.LMT) 110 | order_margin += order.order_margin() 111 | 112 | # 当前权益 = 初始资金 + 历史平仓盈亏 + 当前持仓盈亏 - 历史佣金总额 113 | dh['equity'] = self._init_captial + self.current_holdings['history_profit'] + profit - self.current_holdings['commission'] 114 | dh['cash'] = dh['equity'] - margin - order_margin 115 | if dh['cash'] < 0: 116 | if not is_stock: 117 | # 如果是期货需要追加保证金 118 | ## @bug 如果同时交易期货和股票,就有问题。 119 | raise Exception('需要追加保证金!') 120 | 121 | self.current_holdings['cash'] = dh['cash'] 122 | self.current_holdings['equity'] = dh['equity'] 123 | self.all_holdings.append(dh) 124 | 125 | def update_signal(self, event): 126 | """ 127 | 处理策略函数产生的下单事件。 128 | """ 129 | assert event.type == Event.SIGNAL 130 | valid_orders = [] 131 | for order in event.orders: 132 | if self._valid_order(order): 133 | self.api.order(order) 134 | valid_orders.append(order) 135 | else: 136 | assert(False and "无效合约") 137 | self._open_orders.extend(valid_orders) 138 | self.all_orders.extend(valid_orders) 139 | #print "Receive %d signals!" % len(event.orders) 140 | #self.generate_naive_order(event.orders) 141 | 142 | def update_fill(self, event): 143 | """ 144 | 处理委托单成交事件。 145 | """ 146 | assert event.type == Event.FILL 147 | t_order = None 148 | for i, order in enumerate(self._open_orders): 149 | if order.id == event.transaction.id: 150 | t_order = self._open_orders.pop(i) 151 | break 152 | assert(t_order) 153 | self._update_positions(t_order, event.transaction) 154 | self._update_holdings(event.transaction) 155 | 156 | 157 | def _update_positions(self, order, trans): 158 | ## @todo 把复杂统计单独出来。 159 | """ 更新持仓 """ 160 | pos = self.current_positions.setdefault(trans.contract, Position(trans)) 161 | if trans.side == TradeSide.KAI: 162 | # 开仓 163 | pos.cost = (pos.cost*pos.quantity + trans.price*trans.quantity) / (pos.quantity+trans.quantity) 164 | pos.quantity += trans.quantity 165 | elif trans.side == TradeSide.PING: 166 | # 平仓 167 | pos.quantity -= trans.quantity 168 | 169 | 170 | def _update_holdings(self, trans): 171 | """ 172 | 更新资金 173 | """ 174 | # 每笔佣金,和数目无关! 175 | self.current_holdings['commission'] += trans.commission 176 | # 平仓,更新历史持仓盈亏。 177 | if trans.side == TradeSide.PING: 178 | multi = 1 if trans.direction == Direction.LONG else -1 179 | profit = (trans.price-self.current_positions[trans.contract].cost) * trans.quantity * multi 180 | self.current_holdings['history_profit'] += profit 181 | self.pp.append(profit) 182 | 183 | self.transactions.append(trans) 184 | 185 | def _valid_order(self, order): 186 | """ 判断订单是否合法。 """ 187 | if order.side == TradeSide.PING: 188 | try: 189 | pos = self.current_positions[order.contract] 190 | if pos.quantity >= order.quantity: 191 | return True 192 | except KeyError: 193 | # 没有持有该合约 194 | logger.warn("不存在合约[%s]" % order.contract) 195 | return False 196 | logger.warn("下单仓位问题") 197 | return False 198 | elif order.side == TradeSide.KAI: 199 | if self.current_holdings['cash'] < 0: 200 | raise Exception('没有足够的资金开仓') 201 | return True 202 | 203 | -------------------------------------------------------------------------------- /quantdigger/kernel/engine/data.py: -------------------------------------------------------------------------------- 1 | # data.py 2 | 3 | import datetime 4 | import os, os.path 5 | import pandas as pd 6 | 7 | from abc import ABCMeta, abstractmethod 8 | 9 | from event import MarketEvent 10 | 11 | # data.py 12 | 13 | class DataHandler(object): 14 | """ 15 | DataHandler is an abstract base class providing an interface for 16 | all subsequent (inherited) data handlers (both live and historic). 17 | 18 | The goal of a (derived) DataHandler object is to output a generated 19 | set of bars (OLHCVI) for each symbol requested. 20 | 21 | This will replicate how a live strategy would function as current 22 | market data would be sent "down the pipe". Thus a historic and live 23 | system will be treated identically by the rest of the backtesting suite. 24 | """ 25 | 26 | __metaclass__ = ABCMeta 27 | 28 | @abstractmethod 29 | def get_latest_bars(self, symbol, N=1): 30 | """ 31 | Returns the last N bars from the latest_symbol list, 32 | or fewer if less bars are available. 33 | """ 34 | raise NotImplementedError("Should implement get_latest_bars()") 35 | 36 | @abstractmethod 37 | def update_bars(self): 38 | """ 39 | Pushes the latest bar to the latest symbol structure 40 | for all symbols in the symbol list. 41 | """ 42 | raise NotImplementedError("Should implement update_bars()") 43 | 44 | 45 | class HistoricCSVDataHandler(DataHandler): 46 | """ 47 | HistoricCSVDataHandler is designed to read CSV files for 48 | each requested symbol from disk and provide an interface 49 | to obtain the "latest" bar in a manner identical to a live 50 | trading interface. 51 | """ 52 | 53 | def __init__(self, events, csv_dir, symbol_list): 54 | """ 55 | Initialises the historic data handler by requesting 56 | the location of the CSV files and a list of symbols. 57 | 58 | It will be assumed that all files are of the form 59 | 'symbol.csv', where symbol is a string in the list. 60 | 61 | Parameters: 62 | events - The Event Queue. 63 | csv_dir - Absolute directory path to the CSV files. 64 | symbol_list - A list of symbol strings. 65 | """ 66 | self.events = events 67 | self.csv_dir = csv_dir 68 | self.symbol_list = symbol_list 69 | 70 | self.symbol_data = {} 71 | self.latest_symbol_data = {} 72 | self.continue_backtest = True 73 | 74 | 75 | def _open_convert_csv_files(self): 76 | """ 77 | Opens the CSV files from the data directory, converting 78 | them into pandas DataFrames within a symbol dictionary. 79 | 80 | For this handler it will be assumed that the data is 81 | taken from DTN IQFeed. Thus its format will be respected. 82 | """ 83 | comb_index = None 84 | for s in self.symbol_list: 85 | # Load the CSV file with no header information, indexed on date 86 | self.symbol_data[s] = pd.io.parsers.read_csv( 87 | os.path.join(self.csv_dir, '%s.csv' % s), 88 | header=0, index_col=0, 89 | names=['datetime','open','low','high','close','volume','oi'] 90 | ) 91 | 92 | # Combine the index to pad forward values 93 | if comb_index is None: 94 | comb_index = self.symbol_data[s].index 95 | else: 96 | comb_index.union(self.symbol_data[s].index) 97 | 98 | # Set the latest symbol_data to None 99 | self.latest_symbol_data[s] = [] 100 | 101 | # Reindex the dataframes 102 | for s in self.symbol_list: 103 | self.symbol_data[s] = self.symbol_data[s].reindex(index=comb_index, method='pad').iterrows() 104 | 105 | def _get_new_bar(self, symbol): 106 | """ 107 | Returns the latest bar from the data feed as a tuple of 108 | (sybmbol, datetime, open, low, high, close, volume). 109 | """ 110 | for b in self.symbol_data[symbol]: 111 | yield tuple([symbol, datetime.datetime.strptime(b[0], '%Y-%m-%d %H:%M:%S'), 112 | b[1][0], b[1][1], b[1][2], b[1][3], b[1][4]]) 113 | 114 | def get_latest_bars(self, symbol, N=1): 115 | """ 116 | Returns the last N bars from the latest_symbol list, 117 | or N-k if less available. 118 | """ 119 | try: 120 | bars_list = self.latest_symbol_data[symbol] 121 | except KeyError: 122 | print "That symbol is not available in the historical data set." 123 | else: 124 | return bars_list[-N:] 125 | 126 | def update_bars(self): 127 | """ 128 | Pushes the latest bar to the latest_symbol_data structure 129 | for all symbols in the symbol list. 130 | """ 131 | for s in self.symbol_list: 132 | try: 133 | bar = self._get_new_bar(s).next() 134 | except StopIteration: 135 | self.continue_backtest = False 136 | else: 137 | if bar is not None: 138 | self.latest_symbol_data[s].append(bar) 139 | self.events.put(MarketEvent()) 140 | -------------------------------------------------------------------------------- /quantdigger/kernel/engine/event.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | # event.py 3 | #from flufl.enum import Enum 4 | class EventsPool(object): 5 | """ 事件池,每个策略有一个。""" 6 | _pool = [] 7 | def __init__(self, container=None): 8 | """ container决定是否是线程安全的。 9 | 10 | Args: 11 | container (list or Queue): 事件容器 12 | """ 13 | if container: 14 | self._pool = container 15 | 16 | def put(self, item): 17 | self._pool.append(item) 18 | 19 | def get(self): 20 | return self._pool.pop(0) 21 | 22 | 23 | class Event(object): 24 | """ 事件类型。 25 | 26 | :ivar MARKET: 市场数据事件, 目前未用到。 27 | :ivar SIGNAL: 交易信号事件, 由策略函数产生。 28 | :ivar ORDER: 委托下单事件, 由下单控制器产生。 29 | :ivar FILL: 订单成交事件, 由交易模拟器产生。 30 | """ 31 | MARKET = 1 32 | SIGNAL = 2 33 | ORDER = 3 34 | FILL = 4 35 | 36 | 37 | class MarketEvent(object): 38 | """ 39 | 市场数据到达事件。 40 | """ 41 | 42 | def __init__(self): 43 | """ 44 | Initialises the MarketEvent. 45 | """ 46 | self.type = Event.MARKET 47 | 48 | 49 | class SignalEvent(object): 50 | """ 51 | 由策略函数产生的交易信号事件。 52 | """ 53 | 54 | def __init__(self, orders): 55 | self.type = Event.SIGNAL 56 | self.orders = orders 57 | 58 | 59 | class OrderEvent(object): 60 | """ 61 | 委托下单事件。 62 | """ 63 | 64 | def __init__(self, order): 65 | self.type = Event.ORDER 66 | self.order = order 67 | 68 | 69 | 70 | 71 | class FillEvent(object): 72 | """ 委托成交事件。 """ 73 | def __init__(self, transaction): 74 | self.type = Event.FILL 75 | self.transaction = transaction 76 | #""" 77 | #Encapsulates the notion of a Filled Order, as returned 78 | #from a brokerage. Stores the quantity of an instrument 79 | #actually filled and at what price. In addition, stores 80 | #the commission of the trade from the brokerage. 81 | #""" 82 | 83 | #def __init__(self, timeindex, symbol, exchange, quantity, 84 | #direction, fill_cost, commission=None): 85 | #""" 86 | #Initialises the FillEvent object. Sets the symbol, exchange, 87 | #quantity, direction, cost of fill and an optional 88 | #commission. 89 | 90 | #If commission is not provided, the Fill object will 91 | #calculate it based on the trade size and Interactive 92 | #Brokers fees. 93 | 94 | #Parameters: 95 | #timeindex - The bar-resolution when the order was filled. 96 | #symbol - The instrument which was filled. 97 | #exchange - The exchange where the order was filled. 98 | #quantity - The filled quantity. 99 | #direction - The direction of fill ('BUY' or 'SELL') 100 | #fill_cost - The holdings value in dollars. 101 | #commission - An optional commission sent from IB. 102 | #""" 103 | 104 | #self.type = Event.FILL 105 | #self.timeindex = timeindex 106 | #self.symbol = symbol 107 | #self.exchange = exchange 108 | #self.quantity = quantity 109 | #self.direction = direction 110 | #self.fill_cost = fill_cost 111 | 112 | ## Calculate commission 113 | #if commission is None: 114 | #self.commission = self.calculate_ib_commission() 115 | #else: 116 | #self.commission = commission 117 | 118 | #def calculate_ib_commission(self): 119 | #""" 120 | #Calculates the fees of trading based on an Interactive 121 | #Brokers fee structure for API, in USD. 122 | 123 | #This does not include exchange or ECN fees. 124 | 125 | #Based on "US API Directed Orders": 126 | #https://www.interactivebrokers.com/en/index.php?f=commission&p=stocks2 127 | #""" 128 | #full_cost = 1.3 129 | #if self.quantity <= 500: 130 | #full_cost = max(1.3, 0.013 * self.quantity) 131 | #else: # Greater than 500 132 | #full_cost = max(1.3, 0.008 * self.quantity) 133 | #full_cost = min(full_cost, 0.5 / 100.0 * self.quantity * self.fill_cost) 134 | #return full_cost 135 | -------------------------------------------------------------------------------- /quantdigger/kernel/engine/exchange.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | from quantdigger.kernel.datastruct import Transaction, PriceType, TradeSide, Direction 3 | from quantdigger.kernel.engine.event import FillEvent 4 | class Exchange(object): 5 | """ 模拟交易所。 6 | 7 | :ivar _slippage: 滑点模型。 8 | :ivar _open_orders: 未成交订单。 9 | :ivar events: 事件池。 10 | """ 11 | def __init__(self, events_pool, slippage = None, strict=True): 12 | self._slippage = slippage 13 | self._open_orders = set() 14 | self.events = events_pool 15 | # strict 为False表示只关注信号源的可视化,而非实际成交情况。 16 | self._strict = strict 17 | 18 | def make_market(self, bar): 19 | ## @bug 开仓资金是否足够的验证 20 | ## @todo 21 | """ 价格撮合""" 22 | if self._open_orders: 23 | fill_orders = set() 24 | for order in self._open_orders: 25 | transact = Transaction(order) 26 | if self._strict: 27 | if order.price_type == PriceType.LMT: 28 | # 限价单以最高和最低价格为成交的判断条件. 29 | if (order.side == TradeSide.KAI and \ 30 | (order.direction == Direction.LONG and order.price >= bar.low or \ 31 | order.direction == Direction.SHORT and order.price <= bar.high)) or \ 32 | (order.kpp == TradeSide.PING and \ 33 | (order.direction == Direction.LONG and order.price <= bar.high or \ 34 | order.direction == Direction.SHORT and order.price >= bar.low)): 35 | transact.price = order.price 36 | # Bar的结束时间做为交易成交时间. 37 | transact.datetime = bar.datetime 38 | fill_orders.add(order) 39 | self.events.put(FillEvent(transact)) 40 | elif order.type == PriceType.MKT: 41 | # 市价单以最高或最低价格为成交价格. 42 | if order.side == TradeSide.KAI: 43 | if order.direction == Direction.LONG: 44 | transact.price = bar.high 45 | else: 46 | transact.price = bar.low 47 | elif order.side == TradeSide.PING: 48 | if order.direction == Direction.LONG: 49 | transact.price = bar.low 50 | else: 51 | transact.price = bar.high 52 | 53 | transact.datetime = bar.datetime 54 | fill_orders.add(order) 55 | self.events.put(FillEvent(transact)) 56 | else: 57 | transact.datetime = bar.datetime 58 | fill_orders.add(order) 59 | # 60 | self.events.put(FillEvent(transact)) 61 | if fill_orders: 62 | self._open_orders -= fill_orders 63 | 64 | 65 | def insert_order(self, event): 66 | """ 67 | 模拟交易所收到订单。 68 | """ 69 | self._open_orders.add(event.order) 70 | 71 | def cancel_order(self, order): 72 | pass 73 | 74 | 75 | def update_datetime(self, dt): 76 | self._datetime = dt 77 | 78 | 79 | -------------------------------------------------------------------------------- /quantdigger/kernel/engine/execute_unit.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | import Queue 3 | import pandas as pd 4 | from quantdigger.kernel.datasource.data import local_data 5 | from quantdigger.errors import DataAlignError 6 | from quantdigger.kernel.engine.strategy import BarTracker 7 | from quantdigger.kernel.engine.event import Event 8 | class ExecuteUnit(object): 9 | """ 策略执行的物理单元,支持多个策略同时运行。 10 | 每个执行单元都可能跟踪多个数据(策略用到的周期合约数据集合)。 11 | 其中第一个合约为"主合约" 。 每个执行器可加载多个策略,只要数据需求不超过pcontracts。 12 | 13 | :ivar begin_dt: 策略执行的时间起点。 14 | :vartype begin_dt: datetime 15 | :ivar end_dt: 策略执行的时间终点。 16 | :vartype end_dt: datetime 17 | :ivar pcontracts: 策略用到的周期合约数据集合。 18 | :vartype pcontracts: list 19 | :ivar trackers: 策略用到的跟踪器集合。(和周期合约一一对应) 20 | :vartype trackers: list 21 | :ivar _strategies: 策略集合。 22 | :vartype _strategies: list 23 | 24 | """ 25 | def __init__(self, pcontracts, begin_dt=None, end_dt=None): 26 | self.begin_dt = begin_dt 27 | self.end_dt = end_dt 28 | # 不同周期合约数据。 29 | self.data = { } # PContract -> pandas.DataFrame 30 | self.pcontracts = pcontracts 31 | self.trackers = [] 32 | self._strategies = [] 33 | 34 | # 如果begin_dt, end_dt 等于None,做特殊处理。 35 | # accociate with a mplot widget 36 | #tracker.pcontracts 37 | 38 | self.load_data(pcontracts[0]) 39 | # 每个周期合约对应一个跟跟踪器。 40 | for pcon in pcontracts[1:]: 41 | self.load_data(pcon) 42 | BarTracker(self, pcon) 43 | 44 | def run(self): 45 | """""" 46 | print 'running...' 47 | bar_index = 0 48 | while bar_index < self._data_length: 49 | # 50 | latest_bars = { } 51 | try: 52 | for tracker in self.trackers: 53 | bar = tracker.update_curbar(bar_index) 54 | latest_bars[tracker._main_contract] = bar 55 | # 在回测中无需MARKET事件。 56 | # 这样可以加速回测速度。 57 | for algo in self._strategies: 58 | bar = algo.update_curbar(bar_index) 59 | algo.exchange.update_datetime(bar.datetime) 60 | algo.blotter.update_datetime(bar.datetime) 61 | latest_bars[algo._main_contract] = bar 62 | algo.blotter.update_bar(latest_bars) 63 | #algo.exchange.make_market(bar) 64 | # 对新的价格运行算法。 65 | algo.execute_strategy() 66 | while True: 67 | # 事件处理。 68 | try: 69 | event = algo.events_pool.get() 70 | except Queue.Empty: 71 | break 72 | except IndexError: 73 | break 74 | else: 75 | if event is not None: 76 | #if event.type == 'MARKET': 77 | #strategy.calculate_signals(event) 78 | #port.update_timeindex(event) 79 | if event.type == Event.SIGNAL: 80 | algo.blotter.update_signal(event) 81 | 82 | elif event.type == Event.ORDER: 83 | algo.exchange.insert_order(event) 84 | 85 | elif event.type == Event.FILL: 86 | # 模拟交易接口收到报单成交 87 | algo.blotter.api.on_transaction(event) 88 | # 价格撮合。note: bar价格撮合要求撮合置于运算后面。 89 | algo.exchange.make_market(bar) 90 | 91 | except Exception, e: 92 | print(e) 93 | raise 94 | bar_index += 1 95 | 96 | def load_data(self, pcontract): 97 | """ 加载周期合约数据 98 | 99 | :param PContract pcontract: 周期合约。 100 | :return: 周期合约数据。 101 | :rtype: pandas.DataFrame 102 | """ 103 | try: 104 | return self.data[pcontract] 105 | except KeyError: 106 | data = local_data.load_data(pcontract) 107 | if not hasattr(self, '_data_length'): 108 | self._data_length = len(data) 109 | elif self._data_length != len(data): 110 | raise DataAlignError 111 | data['row'] = pd.Series(xrange(0, len(data.index)), index=data.index) 112 | self.data[pcontract] = data 113 | return data 114 | 115 | def add_tracker(self, tracker): 116 | """ 添加跟踪器。 """ 117 | self.trackers.append(tracker) 118 | 119 | def add_strategy(self, strategy): 120 | """ 添加策略。 """ 121 | self._strategies.append(strategy) 122 | -------------------------------------------------------------------------------- /quantdigger/kernel/engine/series.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | import numpy as np 3 | from quantdigger.errors import SeriesIndexError, BreakConstError 4 | 5 | class SeriesBase(object): 6 | """ 序列变量的基类。 7 | 8 | :ivar _curbar: 当前Bar索引。 9 | :vartype _curbar: int 10 | 11 | :ivar _tracker: 负责维护次时间序列变量的跟踪器。 12 | :vartype _tracker: BarTracker 13 | 14 | :ivar _system_var: 是否是策略内置变量如Strategy.open, Strategy.close 15 | :vartype _system_var: bool 16 | 17 | :ivar data: 数据,类型为支持索引的数据结构,如list, ndarray, pandas.Series等。 18 | """ 19 | DEFAULT_VALUE = None 20 | def __init__(self, tracker, data=[], system_var=False): 21 | # 非向量化运行的普通序列变量的_length_history的值为0. 22 | self._length_history = len(data) 23 | 24 | self._curbar = 0 25 | self._tracker = tracker 26 | self._system_var = system_var 27 | self._added_to_tracker(tracker, system_var) 28 | self.data = data 29 | 30 | @property 31 | def length_history(self): 32 | """ 历史数据长度。 """ 33 | return self._length_history 34 | 35 | @property 36 | def curbar(self): 37 | """ 当前Bar索引。 """ 38 | return self._curbar 39 | 40 | 41 | def update_curbar(self, curbar): 42 | """ 更新当前Bar索引, 被tracker调用。 """ 43 | self._curbar = curbar 44 | 45 | 46 | def _added_to_tracker(self, tracker, system_var): 47 | """ 添加到跟踪器的时间序列变量列表。 48 | 49 | 如果是系统变量open,close,high,low,volume 50 | 那么tracker为None,不负责更新数据。 51 | 系统变量的值有ExcuteUnit更新。 52 | """ 53 | if not system_var: 54 | tracker.add_series(self) 55 | 56 | def append(self, v): 57 | """ 赋值操作 58 | 59 | 非系统序列变量。 60 | python没有'='运算符重载:( 61 | """ 62 | if self._system_var: 63 | raise BreakConstError 64 | self.data[self._curbar] = v 65 | 66 | def __size__(self): 67 | """""" 68 | return len(self.data) 69 | 70 | def duplicate_last_element(self): 71 | """ 更新非系统变量最后一个单元的值。 """ 72 | # 非向量化运行。 73 | if self.length_history == 0: 74 | if self._curbar == 0: 75 | self.data.append(self.DEFAULT_VALUE) 76 | else: 77 | self.data.append(self.data[-1]) 78 | return 79 | # 向量化运行, 并且收到了实时数据。 80 | if self._curbar > self.length_history: 81 | ## @bug 这里假设非系统变量也预留了空间。和子类构造函数不符合。 82 | self.data[self._curbar] = self.data[self._curbar-1] 83 | 84 | def __str__(self): 85 | return str(self.data[self._curbar]) 86 | 87 | def __getitem__(self, index): 88 | try: 89 | i = self._curbar - index 90 | if i < 0: 91 | return self.DEFAULT_VALUE 92 | else: 93 | # index >= len(self.data) 94 | return self.data[i] 95 | except SeriesIndexError: 96 | raise SeriesIndexError 97 | 98 | #def __call__(self, *args): 99 | #length = len(args) 100 | #if length == 0: 101 | #return float(self) 102 | #elif length == 1: 103 | #return self.data[self._curbar - args[0]] 104 | 105 | 106 | class NumberSeries(SeriesBase): 107 | """ 数字序列变量""" 108 | DEFAULT_VALUE = 0.0 109 | value_type = float 110 | def __init__(self, tracker, data=[], system_var=False): 111 | super(NumberSeries, self).__init__(tracker, data, system_var) 112 | # 为当天数据预留空间。 113 | # 系统序列变量总是预留空间。向量化运行中,非系统序列变量的长度 114 | # 计算中会与系统序列变量的长度对齐。非向量化运行的普通序列变量 115 | # 无需预留空间。 116 | if system_var: 117 | self.data = np.append(data, tracker.container_day) 118 | else: 119 | self.data = data 120 | 121 | def __float__(self): 122 | return self.data[self._curbar] 123 | 124 | # 125 | def __eq__(self, r): 126 | self.data[self._curbar] == float(r) 127 | 128 | def __lt__(self, r): 129 | self.data[self._curbar] < float(r) 130 | 131 | def __le__(self, r): 132 | self.data[self._curbar] <= float(r) 133 | 134 | def __ne__(self, r): 135 | self.data[self._curbar] != float(r) 136 | 137 | def __gt__(self, r): 138 | self.data[self._curbar] > float(r) 139 | 140 | def __ge__(self, r): 141 | self.data[self._curbar] >= float(r) 142 | 143 | # 144 | def __iadd__(self, r): 145 | self.data[self._curbar] += float(r) 146 | return self 147 | 148 | def __isub__(self, r): 149 | self.data[self._curbar] -= float(r) 150 | return self 151 | 152 | def __imul__(self, r): 153 | self.data[self._curbar] *= float(r) 154 | return self 155 | 156 | def __idiv__(self, r): 157 | self.data[self._curbar] /= float(r) 158 | return self 159 | 160 | def __ifloordiv__(self, r): 161 | self.data[self._curbar] %= float(r) 162 | return self 163 | 164 | # 165 | def __add__(self, r): 166 | return self.data[self._curbar] + float(r) 167 | 168 | def __sub__(self, r): 169 | return self.data[self._curbar] - float(r) 170 | 171 | def __mul__(self, r): 172 | return self.data[self._curbar] * float(r) 173 | 174 | def __div__(self, r): 175 | return self.data[self._curbar] / float(r) 176 | 177 | def __mod__(self, r): 178 | return self.data[self._curbar] % float(r) 179 | 180 | def __pow__(self, r): 181 | return self.data[self._curbar] ** float(r) 182 | 183 | # 184 | def __radd__(self, r): 185 | return self.data[self._curbar] + float(r) 186 | 187 | def __rsub__(self, r): 188 | return self.data[self._curbar] - float(r) 189 | 190 | def __rmul__(self, r): 191 | return self.data[self._curbar] * float(r) 192 | 193 | def __rdiv__(self, r): 194 | return self.data[self._curbar] / float(r) 195 | 196 | def __rmod__(self, r): 197 | return self.data[self._curbar] % float(r) 198 | 199 | def __rpow__(self, r): 200 | return self.data[self._curbar] ** float(r) 201 | 202 | class DateTimeSeries(SeriesBase): 203 | """ 时间序列变量 """ 204 | ## @todo utc 技时起点 205 | DEFAULT_VALUE = 0.0 206 | def __init__(self, tracker, data=[], system_var=False): 207 | super(DateTimeSeries, self).__init__(tracker, data, system_var) 208 | ## @todo 预留空间 209 | # 为当天数据预留空间。 210 | # 系统序列变量总是预留空间。向量化运行中,非系统序列变量的长度 211 | # 计算中会与系统序列变量的长度对齐。非向量化运行的普通序列变量 212 | # 无需预留空间。 213 | #if system_var: 214 | #self.data = np.append(data, tracker.container_day) 215 | #else: 216 | self.data = data 217 | -------------------------------------------------------------------------------- /quantdigger/kernel/engine/strategy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | import numpy as np 3 | 4 | from quantdigger.kernel.datastruct import ( 5 | Order, 6 | Bar, 7 | TradeSide, 8 | Direction, 9 | Contract, 10 | PriceType 11 | ) 12 | 13 | from quantdigger.kernel.engine.exchange import Exchange 14 | from quantdigger.kernel.engine.event import SignalEvent 15 | from quantdigger.kernel.engine.series import NumberSeries, DateTimeSeries 16 | from quantdigger.util import engine_logger as logger 17 | 18 | from blotter import SimpleBlotter 19 | from event import EventsPool 20 | 21 | class CrossTrackerMixin(object): 22 | """ 夸品种数据引用。 23 | 24 | 索引为0表示主合约。索引大于1表示夸品种数据引用。 25 | 用于套利等策略。 26 | """ 27 | def open_(self, index): 28 | assert(index >= 0) 29 | if index == 0: 30 | return self.open 31 | else: 32 | return self._excution.trackers[index-1].open 33 | 34 | def close_(self, index): 35 | assert(index >= 0) 36 | if index == 0: 37 | return self.close 38 | else: 39 | return self._excution.trackers[index-1].close 40 | 41 | def high_(self, index): 42 | assert(index >= 0) 43 | if index == 0: 44 | return self.high 45 | else: 46 | return self._excution.trackers[index-1].high 47 | 48 | def low_(self, index): 49 | assert(index >= 0) 50 | if index == 0: 51 | return self.low 52 | else: 53 | return self._excution.trackers[index-1].low 54 | 55 | def volume_(self, index): 56 | assert(index >= 0) 57 | if index == 0: 58 | return self.volume 59 | else: 60 | return self._excution.trackers[index-1].volume 61 | 62 | def datetime_(self, index): 63 | assert(index >= 0) 64 | if index == 0: 65 | return self.datetime 66 | else: 67 | return self._excution.trackers[index-1].datetime 68 | 69 | 70 | class BarTracker(object): 71 | """ 跟踪器,可能是策略,策略用到的非主合约数据,独立指标。 72 | 73 | :ivar events_pool: 事件池。 74 | :ivar blotter: 订单本地处理器。 75 | :ivar exchange: 模拟交易所。 76 | :ivar _excution: 最小执行单元。 77 | :ivar _series: 当前跟踪器负责维护的时间序列变量集合。 78 | :ivar _main_pcontract: 主合约。即self.open等指向的合约。 79 | :ivar open: 主合约当前Bar的开盘价。 80 | :ivar close: 主合约当前Bar的收盘价。 81 | :ivar high: 主合约当前Bar的最高价。 82 | :ivar low: 主合约当前Bar的最低价。 83 | :ivar volume: 主合约当前Bar的成交量。 84 | :ivar datetime: 主合约当前Bar的开盘时间。 85 | :ivar curbar: 当前Bar索引。 86 | """ 87 | def __init__(self, exe_unit, pcontract=None): 88 | """ 初始化数据列表 89 | 90 | Args: 91 | pcontract (PContract): 周期合约, 空表示仅是跟踪器,非策略。 92 | 93 | """ 94 | self.events_pool = EventsPool() 95 | self.blotter = SimpleBlotter(None, self.events_pool) 96 | self.exchange = Exchange(self.events_pool, strict=False) 97 | 98 | self._excution = exe_unit 99 | # tracker中用到的时间序列 100 | self._series = [] 101 | try: 102 | if pcontract: 103 | self._main_pcontract = pcontract 104 | exe_unit.add_tracker(self) 105 | else: 106 | self._main_pcontract = exe_unit.pcontracts[0] 107 | exe_unit.add_strategy(self) 108 | self._main_contract = self._main_pcontract.contract 109 | self._data = exe_unit.data[self._main_pcontract] 110 | self._container_day = np.zeros(shape=(self.length_day(self._main_pcontract), ), dtype = float) 111 | self._init_main_data() 112 | except KeyError: 113 | ## @todo 提醒用户用法。 114 | raise KeyError 115 | 116 | def length_day(self, pcontract): 117 | """ 计算当天的数据量 """ 118 | ## @todo local_data 119 | return 4 120 | 121 | @property 122 | def container_day(self): 123 | """ 为当天数据预留的空间 """ 124 | return self._container_day 125 | 126 | def _init_main_data(self): 127 | # 预留了历史和当天的数据空间。 128 | self.open = NumberSeries(self, self._data.open, True) 129 | self.close = NumberSeries(self, self._data.close, True) 130 | self.high = NumberSeries(self, self._data.high, True) 131 | self.low = NumberSeries(self, self._data.low, True) 132 | self.volume = NumberSeries(self, self._data.volume, True) 133 | self.datetime = DateTimeSeries(self, self._data.index, True) 134 | self.curbar = 0 135 | 136 | def on_tick(self): 137 | """ tick数据到达时候触发。 """ 138 | pass 139 | 140 | def on_bar(self): 141 | """ Bar数据到达时候触发。""" 142 | pass 143 | 144 | def execute_strategy(self): 145 | self.on_tick() 146 | self.on_bar() 147 | 148 | def add_series(self, series): 149 | """ 添加时间序列变量。 150 | 151 | 每个跟踪器都要维护策略使用的时间序列变量,当新的Bar数据 152 | 到达后,通过BarTracker.update_curbar函数更新时间序列变量的最 153 | 后一个值。 154 | """ 155 | self._series.append(series) 156 | 157 | def update_curbar(self, index): 158 | """ 新的bar数据到达时,更新相关信息, 159 | 如其负责维护的时间序列对象变量的最新值。 160 | 161 | :param int index: 当前bar索引。 162 | :return: 最新的Bar对象。 163 | :rtype: Bar 164 | """ 165 | self.curbar = index 166 | self.open.update_curbar(index) 167 | self.close.update_curbar(index) 168 | self.high.update_curbar(index) 169 | self.low.update_curbar(index) 170 | self.volume.update_curbar(index) 171 | self.datetime.update_curbar(index) 172 | 173 | for serie in self._series: 174 | serie.update_curbar(index) 175 | serie.duplicate_last_element() 176 | 177 | return Bar(self.datetime[0], 178 | self.open[0], self.close[0], 179 | self.high[0], self.low[0], 180 | self.volume[0]) 181 | 182 | 183 | class TradingStrategy(BarTracker, CrossTrackerMixin): 184 | """ 策略的基类。 185 | 186 | 负责维护用到的时间序列变量和夸品种数据引用, 187 | 下单,查询等。 188 | """ 189 | def __init__(self, exe): 190 | super(TradingStrategy, self).__init__(exe, pcontract=None) 191 | self._indicators = [] 192 | self._orders = [] 193 | 194 | def add_indicator(self, indic): 195 | self._indicators.append(indic) 196 | 197 | def execute_strategy(self): 198 | super(TradingStrategy, self).execute_strategy() 199 | # 一次策略循环可能产生多个委托单。 200 | if self._orders: 201 | self.events_pool.put(SignalEvent(self._orders)) 202 | self._orders = [] 203 | 204 | def buy(self, direction, price, quantity, price_type='LMT', contract=None): 205 | """ 开仓。 206 | 207 | :param str/int direction: 下单方向。多头 - 'long' / 1 ;空头 - 'short' / 2 208 | :param float price: 价格。 209 | :param int quantity: 数量。 210 | :param str/int price_type: 下单价格类型。限价单 - 'lmt' / 1;市价单 - 'mkt' / 2 211 | """ 212 | contract = None 213 | con = Contract(contract) if contract else self._main_contract 214 | self._orders.append(Order( 215 | self.datetime, 216 | con, 217 | PriceType.arg_to_type(price_type), 218 | TradeSide.KAI, 219 | Direction.arg_to_type(direction), 220 | float(price), 221 | quantity 222 | )) 223 | 224 | def sell(self, direction, price, quantity, price_type='MKT', contract=None): 225 | """ 平仓。 226 | 227 | :param str/int direction: 下单方向。多头 - 'long' / 1 ;空头 - 'short' / 2 228 | :param float price: 价格。 229 | :param int quantity: 数量。 230 | :param str/int price_type: 下单价格类型。限价单 - 'lmt' / 1;市价单 - 'mkt' / 2 231 | """ 232 | con = Contract(contract) if contract else self._main_contract 233 | self._orders.append(Order( 234 | self.datetime, 235 | con, 236 | PriceType.arg_to_type(price_type), 237 | TradeSide.PING, 238 | Direction.arg_to_type(direction), 239 | float(price), 240 | quantity 241 | )) 242 | 243 | def position(self, contract=None): 244 | """ 当前仓位。 245 | 246 | :param str contract: 字符串合约,默认为None表示主合约。 247 | :return: 该合约的持仓数目。 248 | :rtype: int 249 | """ 250 | try: 251 | if not contract: 252 | contract = self._main_contract 253 | return self.blotter.current_positions[contract].quantity 254 | except KeyError: 255 | return 0 256 | 257 | def cash(self): 258 | """ 现金。 """ 259 | return self.blotter.current_holdings['cash'] 260 | 261 | def equity(self): 262 | """ 当前权益 """ 263 | return self.blotter.current_holdings['equity'] 264 | 265 | def profit(self, contract=None): 266 | """ 当前持仓的历史盈亏 """ 267 | pass 268 | 269 | def day_profit(self, contract=None): 270 | """ 当前持仓的浮动盈亏 """ 271 | pass 272 | 273 | 274 | def update_curbar(self, index): 275 | """ 除了完成父类函数BarTracker.update_curbar的操作外, 276 | 还计算策略用到的指标如MA的最新值。 277 | 278 | :param int index: 当前Bar索引。 279 | :return: Bar数据。 280 | :rtype: Bar 281 | :raises SeriesIndexError: 如果时间序列索引出错。 282 | """ 283 | 284 | bar = super(TradingStrategy, self).update_curbar(index) 285 | for indicator in self._indicators: 286 | indicator.calculate_latest_element() 287 | return bar 288 | -------------------------------------------------------------------------------- /quantdigger/kernel/engine/todo: -------------------------------------------------------------------------------- 1 | 向量化模式中实时指标函数的测试,类集成。 2 | 加载多个Pcontract要生成对应的tracker,--夸品种和周期的指标。 3 | 4 | y_interval移到画图框架中,这样指标不需要set_yrange() 5 | 指标要么自己实现plot,要么使用元plot函数。 6 | 7 | 回测记录。 8 | 比较两种方式的时间差。 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | # 17 | 18 | -------------------------------------------------------------------------------- /quantdigger/kernel/engine/trader.py: -------------------------------------------------------------------------------- 1 | 2 | from abc import ABCMeta, abstractmethod 3 | import time 4 | 5 | from api import CtpTraderApi 6 | 7 | from event import FillEvent, OrderEvent 8 | import datetime 9 | 10 | class Trader(object): 11 | """ 12 | The ExecutionHandler abstract class handles the interaction 13 | between a set of order objects generated by a Portfolio and 14 | the ultimate set of Fill objects that actually occur in the 15 | market. 16 | 17 | The handlers can be used to subclass simulated Traderages 18 | or live Traderages, with identical interfaces. This allows 19 | strategies to be backtested in a very similar manner to the 20 | live trading engine. 21 | """ 22 | 23 | __metaclass__ = ABCMeta 24 | 25 | @abstractmethod 26 | def execute_order(self, event): 27 | """ 28 | Takes an Order event and executes it, producing 29 | a Fill event that gets placed onto the Events queue. 30 | 31 | Parameters: 32 | event - Contains an Event object with order information. 33 | """ 34 | raise NotImplementedError("Should implement execute_order()") 35 | 36 | 37 | class SimulateTrader(Trader): 38 | """ 39 | Handles order execution via the Interactive Traders 40 | API, for use against accounts when trading live 41 | directly. 42 | """ 43 | 44 | def __init__(self, events, 45 | order_routing="SMART", 46 | currency="USD"): 47 | """ 48 | Initialises the IBExecutionHandler instance. 49 | """ 50 | self.events = events 51 | self.order_routing = order_routing 52 | self.currency = currency 53 | self.fill_dict = {} 54 | 55 | self.tws_conn = self.create_trader() 56 | self.order_id = self.create_initial_order_id() 57 | self.register_handlers() 58 | 59 | 60 | def _error_handler(self, msg): 61 | """ 62 | Handles the capturing of error messages 63 | """ 64 | # Currently no error handling. 65 | print "Server Error: %s" % msg 66 | 67 | 68 | def _reply_handler(self, msg): 69 | """ 70 | Handles of server replies 71 | """ 72 | ## Handle open order orderId processing 73 | #if msg.typeName == "openOrder" and \ 74 | #msg.orderId == self.order_id and \ 75 | #not self.fill_dict.has_key(msg.orderId): 76 | #self.create_fill_dict_entry(msg) 77 | ## Handle Fills 78 | #if msg.typeName == "orderStatus" and \ 79 | #msg.status == "Filled" and \ 80 | #self.fill_dict[msg.orderId]["filled"] == False: 81 | #self.create_fill(msg) 82 | print "Server Response: %s, %s\n" % (msg.typeName, msg) 83 | 84 | 85 | def create_trader(self): 86 | """ 87 | Connect to the Trader Workstation (TWS) running on the 88 | usual port of 7496, with a clientId of 10. 89 | The clientId is chosen by us and we will need 90 | separate IDs for both the execution connection and 91 | market data connection, if the latter is used elsewhere. 92 | """ 93 | tws_conn = CtpTraderApi() 94 | tws_conn.connect() 95 | return tws_conn 96 | 97 | 98 | def create_initial_order_id(self): 99 | """ 100 | Creates the initial order ID used for Interactive 101 | Traders to keep track of submitted orders. 102 | """ 103 | # There is scope for more logic here, but we 104 | # will use "1" as the default for now. 105 | return 1 106 | 107 | 108 | def register_handlers(self): 109 | """ 110 | Register the error and server reply 111 | message handling functions. 112 | """ 113 | ## Assign the error handling function defined above 114 | ## to the TWS connection 115 | #self.tws_conn.register(self._error_handler, 'Error') 116 | 117 | # Assign all of the server reply messages to the 118 | # reply_handler function defined above 119 | self.tws_conn.register_handlers(self._reply_handler) 120 | 121 | 122 | #def create_contract(self, symbol, sec_type, exch, prim_exch, curr): 123 | #""" 124 | #Create a Contract object defining what will 125 | #be purchased, at which exchange and in which currency. 126 | 127 | #symbol - The ticker symbol for the contract 128 | #sec_type - The security type for the contract ('STK' is 'stock') 129 | #exch - The exchange to carry out the contract on 130 | #prim_exch - The primary exchange to carry out the contract on 131 | #curr - The currency in which to purchase the contract 132 | #""" 133 | #contract = Contract() 134 | #contract.m_symbol = symbol 135 | #contract.m_secType = sec_type 136 | #contract.m_exchange = exch 137 | #contract.m_primaryExch = prim_exch 138 | #contract.m_currency = curr 139 | #return contract 140 | 141 | 142 | #def create_order(self, order_type, quantity, action): 143 | #""" 144 | #Create an Order object (Market/Limit) to go long/short. 145 | 146 | #order_type - 'MKT', 'LMT' for Market or Limit orders 147 | #quantity - Integral number of assets to order 148 | #action - 'BUY' or 'SELL' 149 | #""" 150 | #order = Order() 151 | #order.m_orderType = order_type 152 | #order.m_totalQuantity = quantity 153 | #order.m_action = action 154 | #return order 155 | 156 | 157 | #def create_fill_dict_entry(self, msg): 158 | #""" 159 | #Creates an entry in the Fill Dictionary that lists 160 | #orderIds and provides security information. This is 161 | #needed for the event-driven behaviour of the IB 162 | #server message behaviour. 163 | #""" 164 | #self.fill_dict[msg.orderId] = { 165 | #"symbol": msg.contract.m_symbol, 166 | #"exchange": msg.contract.m_exchange, 167 | #"direction": msg.order.m_action, 168 | #"filled": False 169 | #} 170 | 171 | 172 | #def create_fill(self, msg): 173 | #""" 174 | #Handles the creation of the FillEvent that will be 175 | #placed onto the events queue subsequent to an order 176 | #being filled. 177 | #""" 178 | #fd = self.fill_dict[msg.orderId] 179 | 180 | ## Prepare the fill data 181 | #symbol = fd["symbol"] 182 | #exchange = fd["exchange"] 183 | #filled = msg.filled 184 | #direction = fd["direction"] 185 | #fill_cost = msg.avgFillPrice 186 | 187 | ## Create a fill event object 188 | #fill_event = FillEvent( 189 | #datetime.datetime.utcnow(), symbol, 190 | #exchange, filled, direction, fill_cost 191 | #) 192 | 193 | ## Make sure that multiple messages don't create 194 | ## additional fills. 195 | #self.fill_dict[msg.orderId]["filled"] = True 196 | 197 | ## Place the fill event onto the event queue 198 | #self.events.put(fill_event) 199 | 200 | 201 | def execute_order(self, event): 202 | """ 203 | Creates the necessary InteractiveTraders order object 204 | and submits it to IB via their API. 205 | 206 | The results are then queried in order to generate a 207 | corresponding Fill object, which is placed back on 208 | the event queue. 209 | 210 | Parameters: 211 | event - Contains an Event object with order information. 212 | """ 213 | #if event.type == 'ORDER': 214 | ## Prepare the parameters for the asset order 215 | #asset = event.symbol 216 | #asset_type = "STK" 217 | #order_type = event.order_type 218 | #quantity = event.quantity 219 | #direction = event.direction 220 | 221 | ## Create the Interactive Traders contract via the 222 | ## passed Order event 223 | #ib_contract = self.create_contract( 224 | #asset, asset_type, self.order_routing, 225 | #self.order_routing, self.currency 226 | #) 227 | 228 | ## Create the Interactive Traders order via the 229 | ## passed Order event 230 | #ib_order = self.create_order( 231 | #order_type, quantity, direction 232 | #) 233 | 234 | ## Use the connection to the send the order to IB 235 | #self.tws_conn.placeOrder( 236 | #self.order_id, ib_contract, ib_order 237 | #) 238 | 239 | ## NOTE: This following line is crucial. 240 | ## It ensures the order goes through! 241 | #time.sleep(1) 242 | 243 | ## Increment the order ID for this session 244 | #self.order_id += 1 245 | pass 246 | -------------------------------------------------------------------------------- /quantdigger/kernel/indicators/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/quantdigger/kernel/indicators/__init__.py -------------------------------------------------------------------------------- /quantdigger/kernel/indicators/common.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | ## 3 | # @file common.py 4 | # @brief 5 | # @author dingjie.wang@foxmail.com 6 | # @version 0.1 7 | # @date 2015-12-23 8 | import talib 9 | import numpy as np 10 | from indicator import IndicatorBase, transform2ndarray 11 | import matplotlib.finance as finance 12 | from quantdigger.widgets.mplotwidgets.mplots import create_attributes 13 | 14 | class MA(IndicatorBase): 15 | """ 移动平均线指标。 """ 16 | ## @todo assure name is unique, it there are same names, 17 | # modify the repeat name. 18 | @create_attributes 19 | def __init__(self, tracker, prices, n, name='MA', color='y', lw=1, style="line"): 20 | super(MA, self).__init__(tracker, name) 21 | # self.value为任何支持index的数据结构。 22 | # 在策略中,price变量可能为NumberSeries,需要用NUMBER_SERIES_SUPPORT处理, 23 | # 转化为numpy.ndarray等能被指标函数处理的参数。 24 | self.value = self._moving_average(prices, n) 25 | #self._algo = self._iter_moving_average 26 | #self._args = (n,) 27 | 28 | def _iter_moving_average(self, price, n): 29 | """ 逐步运行的均值函数。""" 30 | pass 31 | 32 | def _moving_average(self, data, n): 33 | """ 向量化运行的均值函数。 """ 34 | data = transform2ndarray(data) 35 | return talib.SMA(data, n) 36 | 37 | def plot(self, widget): 38 | """ 绘图,参数可由UI调整。 """ 39 | self.widget = widget 40 | self.plot_line(self.value, self.color, self.lw, self.style) 41 | 42 | #@override_attributes 43 | #def plot(self, widget, color='y', lw=2, style="line"): 44 | #""" 绘图,参数可由UI调整。 45 | ### @note 构造函数中的绘图参数需与函数 46 | #的绘图函数一致。这样函数参数可覆盖默认的属性参数。 47 | #""" 48 | #self.widget = widget 49 | #self.plot_line(self.value, color, lw, style) 50 | 51 | 52 | class BOLL(IndicatorBase): 53 | """ 布林带指标。 """ 54 | ## @todo assure name is unique, it there are same names, 55 | # modify the repeat name. 56 | @create_attributes 57 | def __init__(self, tracker, prices, n, name='BOLL', color='y', lw=1, style="line"): 58 | super(BOLL, self).__init__(tracker, name) 59 | # self.value为任何支持index的数据结构。 60 | # 在策略中,price变量可能为NumberSeries,需要用NUMBER_SERIES_SUPPORT处理, 61 | # 转化为numpy.ndarray等能被指标函数处理的参数。 62 | self.value = self._boll(prices, n) 63 | 64 | def _boll(self, data, n): 65 | """ 向量化运行的均值函数。 """ 66 | data = transform2ndarray(data) 67 | upper, middle, lower = talib.BBANDS(data, n, 2, 2) 68 | return (upper, middle, lower) 69 | 70 | def plot(self, widget): 71 | """ 绘图,参数可由UI调整。 """ 72 | self.widget = widget 73 | #self.plot_line(self.value, self.color, self.lw, self.style) 74 | 75 | 76 | class RSI(IndicatorBase): 77 | @create_attributes 78 | def __init__(self, tracker, prices, n=14, name="RSI", fillcolor='b'): 79 | super(RSI, self).__init__(tracker, name) 80 | self.value = self._relative_strength(prices, n) 81 | ## @todo 一种绘图风格 82 | # 固定的y范围 83 | self.stick_yrange([0, 100]) 84 | 85 | def _relative_strength(self, prices, n=14): 86 | deltas = np.diff(prices) 87 | seed = deltas[:n+1] 88 | up = seed[seed>=0].sum()/n 89 | down = -seed[seed<0].sum()/n 90 | rs = up/down 91 | rsi = np.zeros_like(prices) 92 | rsi[:n] = 100. - 100./(1.+rs) 93 | 94 | for i in range(n, len(prices)): 95 | delta = deltas[i-1] # cause the diff is 1 shorter 96 | if delta>0: 97 | upval = delta 98 | downval = 0. 99 | else: 100 | upval = 0. 101 | downval = -delta 102 | 103 | up = (up*(n-1) + upval)/n 104 | down = (down*(n-1) + downval)/n 105 | 106 | rs = up/down 107 | rsi[i] = 100. - 100./(1.+rs) 108 | return rsi 109 | 110 | def plot(self, widget): 111 | textsize = 9 112 | widget.plot(self.value, color=self.fillcolor, lw=2) 113 | widget.axhline(70, color=self.fillcolor, linestyle='-') 114 | widget.axhline(30, color=self.fillcolor, linestyle='-') 115 | widget.fill_between(self.value, 70, where=(self.value>=70), 116 | facecolor=self.fillcolor, edgecolor=self.fillcolor) 117 | widget.fill_between(self.value, 30, where=(self.value<=30), 118 | facecolor=self.fillcolor, edgecolor=self.fillcolor) 119 | widget.text(0.6, 0.9, '>70 = overbought', va='top', transform=widget.transAxes, fontsize=textsize, color = 'k') 120 | widget.text(0.6, 0.1, '<30 = oversold', transform=widget.transAxes, fontsize=textsize, color = 'k') 121 | widget.set_ylim(0, 100) 122 | widget.set_yticks([30,70]) 123 | widget.text(0.025, 0.95, 'rsi (14)', va='top', transform=widget.transAxes, fontsize=textsize) 124 | 125 | 126 | class MACD(IndicatorBase): 127 | @create_attributes 128 | def __init__(self, tracker, prices, nslow, nfast, name='MACD'): 129 | super(MACD, self).__init__(tracker, name) 130 | self.emaslow, self.emafast, self.macd = self._moving_average_convergence(prices, nslow=nslow, nfast=nfast) 131 | self.value = (self.emaslow, self.emafast, self.macd) 132 | 133 | def _moving_average_convergence(x, nslow=26, nfast=12): 134 | """ 135 | compute the MACD (Moving Average Convergence/Divergence) using a fast and slow exponential moving avg' 136 | return value is emaslow, emafast, macd which are len(x) arrays 137 | """ 138 | emaslow = MA(x, nslow, type='exponential').value 139 | emafast = MA(x, nfast, type='exponential').value 140 | return emaslow, emafast, emafast - emaslow 141 | 142 | def plot(self, widget): 143 | self.widget = widget 144 | fillcolor = 'darkslategrey' 145 | nema = 9 146 | ema9 = MA(self.macd, nema, type='exponential').value 147 | widget.plot(self.macd, color='black', lw=2) 148 | widget.plot(self.ema9, color='blue', lw=1) 149 | widget.fill_between(self.macd-ema9, 0, alpha=0.5, facecolor=fillcolor, edgecolor=fillcolor) 150 | 151 | #def _qtplot(self, widget, fillcolor): 152 | #raise NotImplementedError 153 | 154 | class Volume(IndicatorBase): 155 | """ 柱状图。 """ 156 | @create_attributes 157 | def __init__(self, tracker, open, close, volume, name='volume', colorup='r', colordown='b', width=1): 158 | super(Volume, self).__init__(tracker, name) 159 | self.value = transform2ndarray(volume) 160 | 161 | def plot(self, widget): 162 | finance.volume_overlay(widget, self.open, self.close, self.volume, 163 | self.colorup, self.colordown, self.width) 164 | 165 | -------------------------------------------------------------------------------- /quantdigger/kernel/indicators/indicator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | ## 3 | # @file indicator.py 4 | # @brief 5 | # @author wondereamer 6 | # @version 0.1 7 | # @date 2015-02-22 8 | 9 | import numpy as np 10 | import pandas 11 | from quantdigger.kernel.engine import series 12 | from quantdigger.widgets.plotting import PlotingActions 13 | from quantdigger.errors import SeriesIndexError, DataFormatError 14 | 15 | def transform2ndarray(data): 16 | """ 如果是序列变量,返回ndarray """ 17 | if isinstance(data, series.NumberSeries): 18 | data = data.data[:data.length_history] 19 | elif isinstance(data, pandas.Series): 20 | # 处理pandas.Series 21 | data = np.asarray(data) 22 | if type(data) != np.ndarray: 23 | raise DataFormatError 24 | return data 25 | 26 | # 带参数decorator 27 | #def invoke_algo(algo, *arg): 28 | #"""docstring for test""" 29 | #def _algo(method): 30 | #def __algo(self): 31 | #if not self.series.updated: 32 | #self.series = algo(*arg) 33 | #self.series.updated = True 34 | #return method(self) 35 | #return __algo 36 | #return _algo 37 | 38 | 39 | class IndicatorBase(PlotingActions): 40 | ## @todo 把绘图函数分类到父类中。 41 | """ 42 | 指标基类。每个指标的内部数据为序列变量。所以每个指标对象都会与一个跟踪器相关联, 43 | 负责更新其内部的序列变量。 如果是MA这样的单值指标, 重载函数可以使指标对象像序列变量一样被使用。 44 | 如果是多值指标,如布林带,那么会以元组的形式返回多一个序列变量。 45 | 46 | :ivar name: 指标对象名称 47 | :vartype name: str 48 | :ivar _tracker: 关联跟踪器 49 | :vartype _tracker: BarTracker 50 | :ivar value: 向量化运行结果, 用于处理历史数据。 51 | :ivar _series: 单值指标的序列变量或多值指标的序列变量数组。 52 | :ivar _algo: 逐步指标函数。 53 | :ivar _args: 逐步指标函数的参数。 54 | """ 55 | def __init__(self, tracker, name='', widget=None): 56 | super(IndicatorBase, self).__init__(name, widget) 57 | self.name = name 58 | self.value = None 59 | self._algo = None 60 | self._args = None 61 | ## @todo 判断tracker是否为字符串。 62 | if tracker: 63 | self._added_to_tracker(tracker) 64 | self._tracker = tracker 65 | 66 | def calculate_latest_element(self): 67 | """ 被tracker调用,确保内部序列变量总是最新的。 """ 68 | s = self._series 69 | m = False 70 | if isinstance(self._series, list): 71 | s = self._series[0] 72 | m = True 73 | if s.curbar >= s.length_history: 74 | if not m: 75 | self._series.update(apply(self._algo, self._args)) 76 | else: 77 | rst = apply(self._algo, self._args) 78 | for i, v in enumerate(rst): 79 | self._series[i].update(v) 80 | 81 | 82 | def __size__(self): 83 | """""" 84 | if isinstance(self._series, list): 85 | return len(self._series[0]) 86 | else: 87 | return len(self._series) 88 | 89 | def _added_to_tracker(self, tracker): 90 | if tracker: 91 | tracker.add_indicator(self) 92 | 93 | 94 | def __tuple__(self): 95 | """ 返回元组。某些指标,比如布林带有多个返回值。 96 | 这里以元组的形式返回多个序列变量。 97 | """ 98 | if isinstance(self._series, list): 99 | return tuple(self._series) 100 | else: 101 | return (self._series,) 102 | 103 | #def __iter__(self): 104 | #"""docstring for __iter__""" 105 | #if self._series.__class__.__name__ == 'list': 106 | #return tuple(self._series) 107 | #else: 108 | #return (self._series,) 109 | 110 | def __float__(self): 111 | return self._series[0] 112 | 113 | def __str__(self): 114 | return str(self._series[0]) 115 | 116 | # 117 | def __eq__(self, r): 118 | return float(self) == float(r) 119 | 120 | def __lt__(self, other): 121 | return float(self) < float(other) 122 | 123 | def __le__(self, other): 124 | return float(self) <= float(other) 125 | 126 | def __ne__(self, other): 127 | return float(self) != float(other) 128 | 129 | def __gt__(self, other): 130 | return float(self) > float(other) 131 | 132 | def __ge__(self, other): 133 | return float(self) >= float(other) 134 | 135 | # 以下都是单值函数。 136 | def __getitem__(self, index): 137 | # 大于当前的肯定被运行过。 138 | if index >= 0: 139 | return self._series[index] 140 | else: 141 | raise SeriesIndexError 142 | 143 | # 144 | def __add__(self, r): 145 | return self._series[0] + float(r) 146 | 147 | def __sub__(self, r): 148 | return self._series[0] - float(r) 149 | 150 | def __mul__(self, r): 151 | return self._series[0] * float(r) 152 | 153 | def __div__(self, r): 154 | return self._series[0] / float(r) 155 | 156 | def __mod__(self, r): 157 | return self._series[0] % float(r) 158 | 159 | def __pow__(self, r): 160 | return self._series[0] ** float(r) 161 | 162 | # 163 | def __radd__(self, r): 164 | return self._series[0] + float(r) 165 | 166 | def __rsub__(self, r): 167 | return self._series[0] - float(r) 168 | 169 | def __rmul__(self, r): 170 | return self._series[0] * float(r) 171 | 172 | def __rdiv__(self, r): 173 | return self._series[0] / float(r) 174 | 175 | def __rmod__(self, r): 176 | return self._series[0] % float(r) 177 | 178 | def __rpow__(self, r): 179 | return self._series[0] ** float(r) 180 | 181 | ## 不该被改变。 182 | #def __iadd__(self, r): 183 | #self._series[0] += r 184 | #return self 185 | 186 | #def __isub__(self, r): 187 | #self._series[0] -= r 188 | #return self 189 | 190 | #def __imul__(self, r): 191 | #self._data[self._curbar] *= r 192 | #return self 193 | 194 | #def __idiv__(self, r): 195 | #self._data[self._curbar] /= r 196 | #return self 197 | 198 | #def __ifloordiv__(self, r): 199 | #self._data[self._curbar] %= r 200 | #return self 201 | 202 | -------------------------------------------------------------------------------- /quantdigger/util.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | from logbook import Logger 3 | import time 4 | 5 | engine_logger = Logger('engine') 6 | data_logger = Logger('data') 7 | 8 | def time2int(t): 9 | """ datetime转化为整数。 10 | 11 | :param datetime t: 时间。 12 | :return: 整数。 13 | :rtype: int 14 | """ 15 | epoch = int(time.mktime(t.timetuple())*1000) 16 | return epoch 17 | 18 | def pcontract(contract, period): 19 | """ 构建周期合约结构的便捷方式。 20 | 21 | :param str contract: 合约如:'IF000.SHEF' 22 | :param str Period: 周期如:'10.Minute' 23 | :return: 周期合约 24 | :rtype: PContract 25 | """ 26 | from quantdigger.kernel.datastruct import PContract, Contract, Period 27 | return PContract(Contract(contract), 28 | Period(period)) 29 | 30 | def stock(code): 31 | """ 构建周期合约结构的便捷方式。 32 | 33 | Args: 34 | code (str): 股票代码 35 | 36 | Returns: 37 | PContract. 周期合约 38 | """ 39 | from quantdigger.kernel.datastruct import PContract, Contract, Period 40 | return PContract(Contract('%s.stock' % code), 41 | Period('1.Day')) 42 | -------------------------------------------------------------------------------- /quantdigger/widgets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/quantdigger/widgets/__init__.py -------------------------------------------------------------------------------- /quantdigger/widgets/mplotwidgets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/quantdigger/widgets/mplotwidgets/__init__.py -------------------------------------------------------------------------------- /quantdigger/widgets/mplotwidgets/mplots.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | import numpy as np 3 | import inspect 4 | from matplotlib.colors import colorConverter 5 | from matplotlib.collections import LineCollection, PolyCollection 6 | from quantdigger.kernel.engine import series 7 | 8 | def override_attributes(method): 9 | # 如果plot函数不带绘图参数,则使用属性值做为参数。 10 | # 如果带参数,者指标中的plot函数参数能够覆盖本身的属性。 11 | def wrapper(self, widget, *args, **kwargs): 12 | self.widget = widget 13 | # 用函数中的参数覆盖属性。 14 | arg_names = inspect.getargspec(method).args[2:] 15 | method_args = { } 16 | obj_attrs = { } 17 | for i, arg in enumerate(args): 18 | method_args[arg_names[i]] = arg 19 | method_args.update(kwargs) 20 | 21 | try: 22 | for attr in arg_names: 23 | obj_attrs[attr] = getattr(self, attr) 24 | except Exception, e: 25 | print(e) 26 | print("构造函数和绘图函数的绘图属性参数不匹配。" ) 27 | obj_attrs.update(method_args) 28 | return method(self, widget, **obj_attrs) 29 | return wrapper 30 | 31 | 32 | def create_attributes(method): 33 | """ 根据被修饰函数的参数构造属性。""" 34 | def wrapper(self, *args, **kwargs): 35 | magic = inspect.getargspec(method) 36 | arg_names = magic.args[1:] 37 | # 默认参数 38 | default = dict((x, y) for x, y in zip(magic.args[-len(magic.defaults):], magic.defaults)) 39 | # 调用参数 40 | method_args = { } 41 | for i, arg in enumerate(args): 42 | method_args[arg_names[i]] = arg 43 | method_args.update(kwargs) 44 | # 45 | default.update(method_args) 46 | # 属性创建 47 | for key, value in default.iteritems(): 48 | setattr(self, key, value) 49 | # 构造函数 50 | rst = method(self, *args, **kwargs) 51 | if not hasattr(self, 'value'): 52 | raise Exception("每个指标都必须有value属性,代表指标值!") 53 | else: 54 | # 序列变量 55 | if self.tracker: 56 | if isinstance(self.value, tuple): 57 | self._series = [series.NumberSeries(self.tracker, value) for value in self.value] 58 | else: 59 | self._series = series.NumberSeries(self.tracker, self.value) 60 | # 绘图中的y轴范围未被设置,使用默认值。 61 | if not self.upper: 62 | upper = lower = [] 63 | if isinstance(self.value, tuple): 64 | # 多值指标 65 | upper = [ max([value[i] for value in self.value ]) for i in xrange(0, len(self.value[0]))] 66 | lower = [ min([value[i] for value in self.value ]) for i in xrange(0, len(self.value[0]))] 67 | else: 68 | upper = self.value 69 | lower = self.value 70 | self.set_yrange(lower, upper) 71 | return rst 72 | return wrapper 73 | 74 | class Candles(object): 75 | """ 76 | 画蜡烛线。 77 | """ 78 | def __init__(self, tracker, data, name='candle', width = 0.6, colorup = 'r', colordown='g', lc='k', alpha=1): 79 | """ Represent the open, close as a bar line and high low range as a 80 | vertical line. 81 | 82 | 83 | ax : an Axes instance to plot to 84 | 85 | width : the bar width in points 86 | 87 | colorup : the color of the lines where close >= open 88 | 89 | colordown : the color of the lines where close < open 90 | 91 | alpha : bar transparency 92 | 93 | return value is lineCollection, barCollection 94 | """ 95 | #super(Candles, self).__init__(tracker, name) 96 | self.set_yrange(data.low.values, data.high.values) 97 | self.data = data 98 | self.name = name 99 | self.width = width 100 | self.colorup = colorup 101 | self.colordown = colordown 102 | self.lc = lc 103 | self.alpha = alpha 104 | 105 | # note this code assumes if any value open, close, low, high is 106 | # missing they all are missing 107 | @override_attributes 108 | def plot(self, widget, width = 0.6, colorup = 'r', colordown='g', lc='k', alpha=1): 109 | """docstring for plot""" 110 | delta = self.width/2. 111 | barVerts = [ ( (i-delta, open), (i-delta, close), (i+delta, close), (i+delta, open) ) for i, open, close in zip(xrange(len(self.data)), self.data.open, self.data.close) if open != -1 and close!=-1 ] 112 | rangeSegments = [ ((i, low), (i, high)) for i, low, high in zip(xrange(len(self.data)), self.data.low, self.data.high) if low != -1 ] 113 | r,g,b = colorConverter.to_rgb(self.colorup) 114 | colorup = r,g,b,self.alpha 115 | r,g,b = colorConverter.to_rgb(self.colordown) 116 | colordown = r,g,b,self.alpha 117 | colord = { True : colorup, 118 | False : colordown, 119 | } 120 | colors = [colord[open0 else lower 156 | self.lower = lower 157 | 158 | def y_interval(self, w_left, w_right): 159 | if len(self.upper) == 2: 160 | return max(self.upper), min(self.lower) 161 | ymax = np.max(self.upper[w_left: w_right]) 162 | ymin = np.min(self.lower[w_left: w_right]) 163 | return ymax, ymin 164 | 165 | 166 | class TradingSignal(object): 167 | """ 从信号坐标(时间, 价格)中绘制交易信号。 """ 168 | def __init__(self, tracker, signal, name="Signal", c=None, lw=2): 169 | #self.set_yrange(price) 170 | #self.signal=signal 171 | #self.c = c 172 | #self.lw = lw 173 | self.signal = signal 174 | self.name = name 175 | 176 | def plot(self, widget, c=None, lw=2): 177 | useAA = 0, # use tuple here 178 | signal = LineCollection(self.signal, colors=c, linewidths=lw, 179 | antialiaseds=useAA) 180 | widget.add_collection(signal) 181 | 182 | def y_interval(self, w_left, w_right): 183 | return 0, 100000000 184 | 185 | 186 | class TradingSignalPos(object): 187 | """ 从价格和持仓数据中绘制交易信号图。 """ 188 | def __init__(self, tracker, price_data, deals, name="Signal", c=None, lw=2): 189 | self.signal = [] 190 | self.colors = [] 191 | for deal in deals: 192 | # ((x0, y0), (x1, y1)) 193 | p = ((price_data.row[deal.open_datetime], deal.open_price), 194 | (price_data.row[deal.close_datetime], deal.close_price)) 195 | self.signal.append(p) 196 | self.colors.append((1,0,0,1) if deal.profit() > 0 else (0,1,0,1)) 197 | self.name = name 198 | 199 | def plot(self, widget, lw=2): 200 | useAA = 0, # use tuple here 201 | signal = LineCollection(self.signal, colors=self.colors, linewidths=lw, 202 | antialiaseds=useAA) 203 | widget.add_collection(signal) 204 | 205 | def y_interval(self, w_left, w_right): 206 | ## @todo signal interval 207 | return 0, 100000000 208 | -------------------------------------------------------------------------------- /quantdigger/widgets/mplotwidgets/stock_plot.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/quantdigger/widgets/mplotwidgets/stock_plot.py -------------------------------------------------------------------------------- /quantdigger/widgets/plotting.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | ## 3 | # @file plotting.py 4 | # @brief 5 | # @author wondereamer 6 | # @version 0.15 7 | # @date 2015-06-13 8 | 9 | from matplotlib.axes import Axes 10 | import numpy as np 11 | 12 | class AxWidget(object): 13 | """ matplotlib绘图容器 """ 14 | def __init__(self, name): 15 | self.name = name; 16 | 17 | def plot_line(self, widget, data, color, lw, style='line' ): 18 | widget.plot(data, color=color, lw=lw, label=self.name) 19 | 20 | 21 | class QtWidget(object): 22 | """ pyqt绘图容器 """ 23 | def __init__(self, name): 24 | self.name = name; 25 | 26 | def plot_line(self, widget, data, color, lw, style='line' ): 27 | """ 使用matplotlib容器绘线 """ 28 | raise NotImplementedError 29 | 30 | 31 | class PlotingActions(object): 32 | """ 33 | 系统会提基类。 34 | 35 | 36 | :ivar upper: 坐标上界(绘图用) 37 | :vartype upper: float 38 | :ivar lower: 坐标上界(绘图用) 39 | :vartype lower: float 40 | :ivar widget: 绘图容器,暂定Axes 41 | """ 42 | def __init__(self, name, widget): 43 | self.ax_widget = AxWidget(name) 44 | self.qt_widget = QtWidget(name) 45 | self.widget = widget 46 | self.upper = self.lower = None 47 | 48 | def plot_line(self, data, color, lw, style='line'): 49 | """ 画线 50 | 51 | :ivar data: 浮点数组。 52 | :vartype data: list 53 | :ivar color: 画线颜色 54 | :vartype color: str 55 | :ivar lw: 线宽 56 | :vartype lw: int 57 | """ 58 | # 区分向量绘图和逐步绘图。 59 | if len(data) > 0: 60 | # 区分绘图容器。 61 | if isinstance(self.widget, Axes): 62 | self.ax_widget.plot_line(self.widget, data, color, lw, style) 63 | else: 64 | self.qt_widget.plot_line(self.widget, data, color, lw, style) 65 | else: 66 | pass 67 | 68 | def plot(self, widget): 69 | """ 如需绘制指标,则需重载此函数。 """ 70 | ## @todo 把plot_line等绘图函数分离到widget类中。 71 | pass 72 | 73 | def set_yrange(self, lower, upper): 74 | """ 设置纵坐标范围。 """ 75 | self.lower = lower 76 | self.upper = upper 77 | 78 | def stick_yrange(self, y_range): 79 | """ 固定纵坐标范围。如RSI指标。 80 | 81 | :ivar y_range: 纵坐标范围。 82 | :vartype y_range: list 83 | """ 84 | self.lower = y_range 85 | self.upper = y_range 86 | 87 | def y_interval(self, w_left, w_right): 88 | """ 可视区域移动时候重新计算纵坐标范围。 """ 89 | ## @todo 只存储上下界, 每次缩放的时候计算一次, 在移动时候无需计算。 90 | if len(self.upper) == 2: 91 | # 就两个值,分别代表上下界。 92 | return max(self.upper), min(self.lower) 93 | ymax = np.max(self.upper[w_left: w_right]) 94 | ymin = np.min(self.lower[w_left: w_right]) 95 | return ymax, ymin 96 | 97 | 98 | -------------------------------------------------------------------------------- /quantdigger/widgets/plugin_interface.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | class PluginInterface(object): 4 | """ 插件接口""" 5 | 6 | def __init__(self): 7 | pass 8 | 9 | @abc.abstractmethod 10 | def get_icon(self): 11 | """ 获取插件的图标 """ 12 | pass 13 | 14 | @abc.abstractmethod 15 | def clone(self): 16 | pass 17 | 18 | 19 | -------------------------------------------------------------------------------- /quantdigger/widgets/qtwidgets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andyzsf/quantdigger/2f41fb5aa64187cc42135b53081731f41e113563/quantdigger/widgets/qtwidgets/__init__.py -------------------------------------------------------------------------------- /quantdigger/widgets/qtwidgets/techwidget.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg 3 | from plugin.mplotwidgets.techmplot import TechMPlot 4 | import matplotlib.pyplot as plt 5 | 6 | class TechWidget(TechMPlot, FigureCanvasQTAgg): 7 | def __init__(self, parent=None, *args): 8 | self.fig = plt.figure() 9 | FigureCanvasQTAgg.__init__(self, self.fig) 10 | TechMPlot.__init__(self, self.fig, *args) 11 | self.setParent(parent) 12 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pandas 2 | logbook 3 | TA-Lib == 0.4.8 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | import codecs 3 | import os 4 | import sys 5 | import util 6 | import platform 7 | from distutils.util import convert_path 8 | from fnmatch import fnmatchcase 9 | from setuptools import setup, find_packages 10 | 11 | 12 | def read(fname): 13 | return codecs.open(os.path.join(os.path.dirname(__file__), fname)).read() 14 | 15 | 16 | 17 | standard_exclude = ["*.py", "*.pyc", "*$py.class", "*~", "*.bak"] 18 | standard_exclude_directories = [ 19 | "_darcs", "./build", "./dist", "EGG-INFO", "*.egg-info", "*.bak" 20 | ] 21 | 22 | def find_package_data( 23 | where=".", 24 | package="", 25 | exclude=standard_exclude, 26 | exclude_directories=standard_exclude_directories, 27 | only_in_packages=True, 28 | show_ignored=False): 29 | 30 | out = {} 31 | stack = [(convert_path(where), "", package, only_in_packages)] 32 | while stack: 33 | where, prefix, package, only_in_packages = stack.pop(0) 34 | for name in os.listdir(where): 35 | fn = os.path.join(where, name) 36 | if os.path.isdir(fn): 37 | bad_name = False 38 | for pattern in exclude_directories: 39 | if (fnmatchcase(name, pattern) 40 | or fn.lower() == pattern.lower()): 41 | bad_name = True 42 | if show_ignored: 43 | print >> sys.stderr, ( 44 | "Directory %s ignored by pattern %s" 45 | % (fn, pattern)) 46 | break 47 | if bad_name: 48 | continue 49 | if (os.path.isfile(os.path.join(fn, "__init__.py")) 50 | and not prefix): 51 | if not package: 52 | new_package = name 53 | else: 54 | new_package = package + "." + name 55 | stack.append((fn, "", package, False)) 56 | else: 57 | stack.append((fn, prefix + name + "/", package, only_in_packages)) 58 | elif package or not only_in_packages: 59 | # is a file 60 | bad_name = False 61 | for pattern in exclude: 62 | if (fnmatchcase(name, pattern) 63 | or fn.lower() == pattern.lower()): 64 | bad_name = True 65 | if show_ignored: 66 | print >> sys.stderr, ( 67 | "File %s ignored by pattern %s" 68 | % (fn, pattern)) 69 | break 70 | if bad_name: 71 | continue 72 | out.setdefault(package, []).append(prefix+name) 73 | return out 74 | 75 | 76 | PACKAGE = "quantdigger" 77 | NAME = "QuantDigger" 78 | DESCRIPTION = "量化交易PYTHON回测系统" 79 | AUTHOR = "QuantFans" 80 | AUTHOR_EMAIL = "dingjie.wang@foxmail.com" 81 | URL = "https://github.com/QuantFans/quantdigger" 82 | VERSION = "0.2.00" 83 | 84 | 85 | setup( 86 | name=NAME, 87 | version=VERSION, 88 | description=DESCRIPTION, 89 | long_description=read("./README.rst").decode('utf-8'), 90 | author=AUTHOR, 91 | author_email=AUTHOR_EMAIL, 92 | license="MIT", 93 | url=URL, 94 | packages=find_packages(), 95 | package_data=find_package_data(PACKAGE, only_in_packages=False), 96 | scripts=['util.py','dependency.py','install_pip.py','install.py','install_dependency.py'], 97 | classifiers=[ 98 | "Development Status :: 3 - Alpha", 99 | "Intended Audience :: Developers", 100 | "License :: OSI Approved :: MIT License", 101 | "Operating System :: OS Independent", 102 | "Programming Language :: Python", 103 | ], 104 | zip_safe=False, 105 | ) 106 | -------------------------------------------------------------------------------- /update.rst: -------------------------------------------------------------------------------- 1 | 0.154 2 | ------ 3 | 4 | * 修复股票回测的破产bug 5 | * 把回测金融指标移到digger/finace 6 | * 添加部分数据结构,添加部分数据结构字段 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | todo 17 | ---- 18 | 19 | * 连续3根上涨的k线 20 | -------------------------------------------------------------------------------- /util.py: -------------------------------------------------------------------------------- 1 | def download(url,target): 2 | import sys 3 | if sys.version_info.major >= 3: 4 | import urllib.request,re,sys 5 | data = urllib.request.urlopen(url).read() 6 | else: 7 | import urllib2 8 | data = urllib2.urlopen(url).read() 9 | with open(target,"wb") as code: 10 | code.write(data) 11 | 12 | 13 | def decompress(target,dist): 14 | import tarfile 15 | tar = tarfile.open(target) 16 | names = tar.getnames() 17 | for name in names: 18 | tar.extract(name,dist) 19 | #tar.extractall() 20 | tar.close() 21 | 22 | def decompressZip(target,dist): 23 | import zipfile 24 | f= zipfile.ZipFile(target,'r') 25 | for file in f.namelist(): 26 | f.extract(file,dist) 27 | f.close() 28 | 29 | def printCommandResult(result): 30 | for line in result: 31 | print(line) 32 | --------------------------------------------------------------------------------