diff --git a/README.md b/README.md index 183e72a1..0e3d56fc 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,78 @@ --- + ### 简介 -vn.py是一套基于Python的开源量化交易程序开发框架,起源于国内私募的自主量化交易系统。2015年初项目启动时只是单纯的交易API接口的Python封装。随着业内关注度的上升和社区不断的贡献,目前已经一步步成长为一套全面的交易程序开发框架,用户群体也日渐多样化,包括私募基金、券商自营和资管、期货资管和子公司、高校研究机构和专业个人投资者等。 +vn.py是基于Python的开源量化交易程序开发框架,起源于国内私募的自主量化交易系统。2015年初项目启动时只是单纯的交易API接口的Python封装。随着业内关注度的上升和社区不断的贡献,目前已经成长为一套全功能的交易程序开发框架,用户群体也日渐多样化,包括私募基金、券商自营和资管、期货资管和子公司、高校研究机构和专业个人投资者等。 + +--- + +### 项目结构 + +1. 丰富的Python交易和数据API接口(vnpy.api),基本覆盖了国内外所有常规交易品种(股票、期货、期权、外汇、外盘、比特币),具体包括: + + - CTP(ctp) + + - 飞马(femas) + + - 中泰证券XTP(xtp) + + - 中信证券期权(cshshlp) + + - 金仕达黄金(ksgold) + + - 金仕达期权(ksotp) + + - 飞鼠(sgit) + + - 飞创(xspeed) + + - QDP(qdp) + + - 上海直达期货(shzd) + + - Interactive Brokers(ib) + + - OANDA(oanda) + + - OKCOIN(okcoin) + + - 火币(huobi) + + - 链行(lhang) + + - 通联数据(datayes) + +2. 简洁易用的事件驱动引擎(vnpy.event),作为事件驱动型交易程序的核心 + +3. 支持服务器端数据推送的RPC框架(vnpy.rpc),用于实现多进程分布式架构的交易系统 + +4. 开箱即用的实盘交易平台框架(vnpy.trader),整合了多种交易接口,并针对具体策略算法和功能开发提供了简洁易用的API,用于快速构建交易员所需的量化交易程序,应用举例: + + * 同时登录多个交易接口,在一套界面上监控多种市场的行情和多种资产账户的资金、持仓、委托、成交情况 + + * 支持跨市场套利(CTP期货和LTS证券)、境内外套利(CTP期货和IB外盘)、多市场数据整合实时预测走势(CTP的股指期货数据、IB的外盘A50数据、Wind的行业指数数据)等策略应用 + + * CTA策略引擎模块,在保持易用性的同时,允许用户针对CTA类策略运行过程中委托的报撤行为进行细粒度控制(降低交易滑点、实现高频策略) + + * 实盘行情记录,支持Tick和K线数据的落地,用于策略开发回测以及实盘运行初始化 + +5. 数据相关的API接口(vnpy.data),用于构建和更新历史行情数据库,目前包括: + + * 上海中期历史行情服务(shcifco) + + * 通联数据API下载服务(datayes) + + * 天勤行情数据接口(tq) + +6. 关于vn.py项目的应用演示(examples),对于新手而言可以从这里开始学习vn.py项目的使用方式 + +8. vn.py项目的Docker镜像(docker),目前尚未完成 + +9. [官网](http://vnpy.org)和[知乎专栏](http://zhuanlan.zhihu.com/vn-py),内容包括vn.py项目的开发教程和Python在量化交易领域的应用研究等内容 + +10. 官方交流QQ群262656087,管理较严格(定期清除长期潜水的成员) --- ### 环境准备 @@ -55,9 +124,9 @@ sudo /home/vnpy/anaconda2/bin/conda install -c quantopian ta-lib=0.4.9 1. 在[SimNow](http://simnow.com.cn/)注册CTP仿真账号,记下你的**账号、密码、经纪商编号**,然后下载快期查询你的**交易和行情服务器地址** -2. 找到你的Anaconda安装目录,打开Anaconda2\Lib\site-packages\vnpy-1.6.2b0-py2.7.egg\vnpy\trader\gateway\ctpGateway\CTP_connect.json,修改账号、密码、服务器等为上一步注册完成后你的信息(注意使用专门的编程编辑器,如Sublime Text等,防止json编码出错) +2. 找到vn.py应用示例目录examples,打开examples\VnTrader\CTP_connect.json,修改账号、密码、服务器等为上一步注册完成后你的信息(注意使用专门的编程编辑器,如Sublime Text等,防止json编码出错) -3. 在任意目录将以下内容保存为run.py,并双击运行(若无法双击,则在当前目录按住Shift点鼠标右键,打开cmd输入python run.py运行): +3. 找到VnTrader的启动入口run.py,并双击运行(若无法双击,则在当前目录按住Shift点鼠标右键,打开cmd输入python run.py运行),run.py内容如下: ``` # encoding: UTF-8 @@ -78,12 +147,14 @@ from vnpy.trader.uiQt import createQApp from vnpy.trader.uiMainWindow import MainWindow # 加载底层接口 -from vnpy.trader.gateway import (ctpGateway, oandaGateway, ibGateway, +from vnpy.trader.gateway import (ctpGateway, oandaGateway, ibGateway, huobiGateway, okcoinGateway) if system == 'Windows': - from vnpy.trader.gateway import (femasGateway, xspeedGateway, - sgitGateway, shzdGateway) + from vnpy.trader.gateway import femasGateway, xspeedGateway + +if system == 'Linux': + from vnpy.trader.gateway import xtpGateway # 加载上层应用 from vnpy.trader.app import (riskManager, ctaStrategy, spreadTrading) @@ -94,44 +165,46 @@ def main(): """主程序入口""" # 创建Qt应用对象 qApp = createQApp() - + # 创建事件引擎 ee = EventEngine() - + # 创建主引擎 me = MainEngine(ee) - + # 添加交易接口 me.addGateway(ctpGateway) me.addGateway(oandaGateway) me.addGateway(ibGateway) me.addGateway(huobiGateway) me.addGateway(okcoinGateway) - + if system == 'Windows': me.addGateway(femasGateway) me.addGateway(xspeedGateway) - me.addGateway(sgitGateway) - me.addGateway(shzdGateway) - + + if system == 'Linux': + me.addGateway(xtpGateway) + # 添加上层应用 me.addApp(riskManager) me.addApp(ctaStrategy) me.addApp(spreadTrading) - + # 创建主窗口 mw = MainWindow(me, ee) mw.showMaximized() - + # 在主线程中启动Qt事件循环 sys.exit(qApp.exec_()) if __name__ == '__main__': main() + ``` -以上run.py脚本位于examples\VnTrader目录下,更多使用方法方法请参考examples下的其他目录。 +更多使用方法方法请参考examples下的其他目录。 --- @@ -154,75 +227,6 @@ if __name__ == '__main__': * [Visual Studio 2013](https://www.visualstudio.com/en-us/downloads/download-visual-studio-vs.aspx):这个就不多说了(作者编译API封装用的是2013版本) ---- - -### 项目结构 - -1. 丰富的Python交易和数据API接口(vnpy.api),基本覆盖了国内外所有常规交易品种(股票、期货、期权、外汇、外盘、比特币),具体包括: - - - CTP(ctp) - - - 飞马(femas) - - - LTS(lts) - - - 中信证券期权(cshshlp) - - - 金仕达黄金(ksgold) - - - 金仕达期权(ksotp) - - - 飞鼠(sgit) - - - 飞创(xspeed) - - - QDP(qdp) - - - 上海直达期货(shzd) - - - Interactive Brokers(ib) - - - OANDA(oanda) - - - OKCOIN(okcoin) - - - 火币(huobi) - - - 链行(lhang) - - - 通联数据(datayes) - -2. 简洁易用的事件驱动引擎(vnpy.event),作为事件驱动型交易程序的核心 - -3. 支持服务器端数据推送的RPC框架(vnpy.rpc),用于实现多进程分布式架构的交易系统 - -4. 开箱即用的实盘交易平台框架(vnpy.trader),整合了多种交易接口,并针对具体策略算法和功能开发提供了简洁易用的API,用于快速构建交易员所需的量化交易程序,应用举例: - - * 同时登录多个交易接口,在一套界面上监控多种市场的行情和多种资产账户的资金、持仓、委托、成交情况 - - * 支持跨市场套利(CTP期货和LTS证券)、境内外套利(CTP期货和IB外盘)、多市场数据整合实时预测走势(CTP的股指期货数据、IB的外盘A50数据、Wind的行业指数数据)等策略应用 - - * CTA策略引擎模块,在保持易用性的同时,允许用户针对CTA类策略运行过程中委托的报撤行为进行细粒度控制(降低交易滑点、实现高频策略) - - * 实盘行情记录,支持Tick和K线数据的落地,用于策略开发回测以及实盘运行初始化 - -5. 数据相关的API接口(vnpy.data),用于构建和更新历史行情数据库,目前包括: - - * 上海中期历史行情服务(shcifco) - - * 通联数据API下载服务(datayes) - -5. 关于vn.py项目的应用演示(examples),对于新手而言可以从这里开始学习vn.py项目的使用方式 - -5. 关于项目在实盘交易中的一些使用指南(tutorial) - -6. vn.py项目的Docker镜像(docker),目前尚未完成 - -7. [官网](http://vnpy.org)和[知乎专栏](http://zhuanlan.zhihu.com/vn-py),内容包括vn.py项目的开发教程和Python在量化交易领域的应用研究等内容 - -7. 官方交流QQ群262656087,管理较严格(定期清除长期潜水的成员) - - --- ### 贡献代码 @@ -256,8 +260,13 @@ vn.py使用github托管其源代码,如果希望贡献代码请使用github的 --- -### 联系作者 -作者知乎名:用python的交易员,想要联系作者可以通过知乎私信 +### 其他内容 + +* [获取帮助](https://github.com/vnpy/vnpy/blob/dev/docs/SUPPORT.md) +* [社区行为准侧](https://github.com/vnpy/vnpy/blob/dev/docs/CODE_OF_CONDUCT.md) +* [Issue模板](https://github.com/vnpy/vnpy/blob/dev/docs/ISSUE_TEMPLATE.md) +* [PR模板](https://github.com/vnpy/vnpy/blob/dev/docs/PULL_REQUEST_TEMPLATE.md) + --- ### License diff --git a/docs/SUPPORT.md b/docs/SUPPORT.md index 6a5591fb..a8ee457c 100644 --- a/docs/SUPPORT.md +++ b/docs/SUPPORT.md @@ -1,18 +1,8 @@ # 获取帮助 -* 操作系统: 如Windows 7或者Ubuntu 16.04 -* Anaconda版本: 如Anaconda 4.0.0 Python 2.7 32位 -* vn.py版本: 如v1.7发行版或者dev branch 20170920(下载日期) +在开发和使用vn.py项目的过程中遇到问题时,获取帮助的渠道包括: -## Issue类型 -三选一:Bug/Enhancement/Question - -## 预期程序行为 - - -## 实际程序行为 - - -## 重现步骤 - -针对Bug类型Issue,请提供具体重现步骤以及报错截图 +* Github Issues:[Issues页面](https://github.com/vnpy/vnpy/issues) +* 官方QQ群: 262656087 +* 项目论坛:[维恩的派](http://www.vnpie.com)) +* 项目邮箱: vn.py@foxmail.com diff --git a/examples/DataRecording/DR_setting.json b/examples/DataRecording/DR_setting.json index 07030299..5ec30169 100644 --- a/examples/DataRecording/DR_setting.json +++ b/examples/DataRecording/DR_setting.json @@ -3,16 +3,31 @@ "tick": [ + ["rb1801", "CTP"], + ["m1801", "CTP"], + ["SR801", "CTP"], + ["IF1712", "CTP"], + ["IH1712", "CTP"], + ["IC1712", "CTP"] ], "bar": [ - ["BTC_CNY_SPOT", "OKCOIN"], - ["LTC_CNY_SPOT", "OKCOIN"], - ["ETH_CNY_SPOT", "OKCOIN"] + ["rb1801", "CTP"], + ["m1801", "CTP"], + ["SR801", "CTP"], + ["IF1712", "CTP"], + ["IH1712", "CTP"], + ["IC1712", "CTP"] ], "active": { + "rb.HOT": "rb1801", + "m.HOT": "m1801", + "SR.HOT": "SR801", + "IF.HOT": "IF1712", + "IH.HOT": "IH1712", + "IC.HOT": "IC1712" } } \ No newline at end of file diff --git a/examples/DataRecording/VT_setting.json b/examples/DataRecording/VT_setting.json index ef3bb771..b5b81f40 100644 --- a/examples/DataRecording/VT_setting.json +++ b/examples/DataRecording/VT_setting.json @@ -12,5 +12,7 @@ "logActive": false, "logLevel": "debug", "logConsole": true, - "logFile": true + "logFile": true, + + "tdPenalty": ["IF", "IH", "IC"] } \ No newline at end of file diff --git a/examples/DataRecording/runDataCleaning.py b/examples/DataRecording/runDataCleaning.py new file mode 100644 index 00000000..7cccfeaa --- /dev/null +++ b/examples/DataRecording/runDataCleaning.py @@ -0,0 +1,84 @@ +# encoding: UTF-8 + +import json +from datetime import datetime, timedelta, time + +from pymongo import MongoClient + +from vnpy.trader.app.ctaStrategy.ctaBase import MINUTE_DB_NAME, TICK_DB_NAME + + +# 这里以商品期货为例 +MORNING_START = time(9, 0) +MORNING_REST = time(10, 15) +MORNING_RESTART = time(10, 30) +MORNING_END = time(11, 30) +AFTERNOON_START = time(13, 30) +AFTERNOON_END = time(15, 0) +NIGHT_START = time(21, 0) +NIGHT_END = time(2, 30) + + +#---------------------------------------------------------------------- +def cleanData(dbName, collectionName, start): + """清洗数据""" + print u'\n清洗数据库:%s, 集合:%s, 起始日:%s' %(dbName, collectionName, start) + + mc = MongoClient('localhost', 27017) # 创建MongoClient + cl = mc[dbName][collectionName] # 获取数据集合 + d = {'datetime':{'$gte':start}} # 只过滤从start开始的数据 + cx = cl.find(d) # 获取数据指针 + + # 遍历数据 + for data in cx: + # 获取时间戳对象 + dt = data['datetime'].time() + + # 默认需要清洗 + cleanRequired = True + + # 如果在交易事件内,则为有效数据,无需清洗 + if ((MORNING_START <= dt < MORNING_REST) or + (MORNING_RESTART <= dt < MORNING_END) or + (AFTERNOON_START <= dt < AFTERNOON_END) or + (dt >= NIGHT_START) or + (dt < NIGHT_END)): + cleanRequired = False + + # 如果需要清洗 + if cleanRequired: + print u'删除无效数据,时间戳:%s' %data['datetime'] + cl.delete_one(data) + + print u'清洗完成,数据库:%s, 集合:%s' %(dbName, collectionName) + + + +#---------------------------------------------------------------------- +def runDataCleaning(): + """运行数据清洗""" + print u'开始数据清洗工作' + + # 加载配置 + setting = {} + with open("DR_setting.json") as f: + setting = json.load(f) + + # 遍历执行清洗 + today = datetime.now() + start = today - timedelta(10) # 清洗过去10天数据 + start.replace(hour=0, minute=0, second=0, microsecond=0) + + for l in setting['tick']: + symbol = l[0] + cleanData(TICK_DB_NAME, symbol, start) + + for l in setting['bar']: + symbol = l[0] + cleanData(MINUTE_DB_NAME, symbol, start) + + print u'数据清洗工作完成' + + +if __name__ == '__main__': + runDataCleaning() diff --git a/examples/VnTrader/DR_setting.json b/examples/VnTrader/DR_setting.json index 07030299..5ec30169 100644 --- a/examples/VnTrader/DR_setting.json +++ b/examples/VnTrader/DR_setting.json @@ -3,16 +3,31 @@ "tick": [ + ["rb1801", "CTP"], + ["m1801", "CTP"], + ["SR801", "CTP"], + ["IF1712", "CTP"], + ["IH1712", "CTP"], + ["IC1712", "CTP"] ], "bar": [ - ["BTC_CNY_SPOT", "OKCOIN"], - ["LTC_CNY_SPOT", "OKCOIN"], - ["ETH_CNY_SPOT", "OKCOIN"] + ["rb1801", "CTP"], + ["m1801", "CTP"], + ["SR801", "CTP"], + ["IF1712", "CTP"], + ["IH1712", "CTP"], + ["IC1712", "CTP"] ], "active": { + "rb.HOT": "rb1801", + "m.HOT": "m1801", + "SR.HOT": "SR801", + "IF.HOT": "IF1712", + "IH.HOT": "IH1712", + "IC.HOT": "IC1712" } } \ No newline at end of file diff --git a/vnpy/trader/app/ctaStrategy/ctaEngine.py b/vnpy/trader/app/ctaStrategy/ctaEngine.py index 7065964d..a13177c3 100644 --- a/vnpy/trader/app/ctaStrategy/ctaEngine.py +++ b/vnpy/trader/app/ctaStrategy/ctaEngine.py @@ -219,7 +219,9 @@ class CtaEngine(object): del self.workingStopOrderDict[stopOrderID] # 从策略委托号集合中移除 - self.strategyOrderDict[strategy.name].remove(stopOrderID) + s = self.strategyOrderDict[strategy.name] + if stopOrderID in s: + s.remove(stopOrderID) # 通知策略 strategy.onStopOrder(so) @@ -251,7 +253,9 @@ class CtaEngine(object): del self.workingStopOrderDict[so.stopOrderID] # 从策略委托号集合中移除 - self.strategyOrderDict[so.strategy.name].remove(so.stopOrderID) + s = self.strategyOrderDict[so.strategy.name] + if so.stopOrderID in s: + s.remove(so.stopOrderID) # 更新停止单状态,并通知策略 so.status = STOPORDER_TRIGGERED @@ -285,12 +289,16 @@ class CtaEngine(object): """处理委托推送""" order = event.dict_['data'] - if order.vtOrderID in self.orderStrategyDict: - strategy = self.orderStrategyDict[order.vtOrderID] + vtOrderID = order.vtOrderID + + if vtOrderID in self.orderStrategyDict: + strategy = self.orderStrategyDict[vtOrderID] # 如果委托已经完成(拒单、撤销、全成),则从活动委托集合中移除 if order.status in self.STATUS_FINISHED: - self.strategyOrderDict[strategy.name].remove(order.vtOrderID) + s = self.strategyOrderDict[strategy.name] + if vtOrderID in s: + s.remove(vtOrderID) self.callStrategyFunc(strategy, strategy.onOrder, order) @@ -613,8 +621,9 @@ class CtaEngine(object): """全部撤单""" s = self.strategyOrderDict[name] - # 遍历集合,全部撤单 - for orderID in s: + # 遍历列表,全部撤单 + # 这里不能直接遍历集合s,因为撤单时会修改s中的内容,导致出错 + for orderID in list(s): if STOPORDERPREFIX in orderID: self.cancelStopOrder(orderID) else: diff --git a/vnpy/trader/app/ctaStrategy/ctaHistoryData.py b/vnpy/trader/app/ctaStrategy/ctaHistoryData.py index 70caad34..6e3b77fe 100644 --- a/vnpy/trader/app/ctaStrategy/ctaHistoryData.py +++ b/vnpy/trader/app/ctaStrategy/ctaHistoryData.py @@ -430,6 +430,44 @@ def loadTbCsv(fileName, dbName, symbol): print bar.date, bar.time print u'插入完毕,耗时:%s' % (time()-start) + + #---------------------------------------------------------------------- +def loadTbPlusCsv(fileName, dbName, symbol): + """将TB极速版导出的csv格式的历史分钟数据插入到Mongo数据库中""" + import csv + + start = time() + print u'开始读取CSV文件%s中的数据插入到%s的%s中' %(fileName, dbName, symbol) + + # 锁定集合,并创建索引 + client = pymongo.MongoClient(globalSetting['mongoHost'], globalSetting['mongoPort']) + collection = client[dbName][symbol] + collection.ensure_index([('datetime', pymongo.ASCENDING)], unique=True) + + # 读取数据和插入到数据库 + reader = csv.reader(file(fileName, 'r')) + for d in reader: + bar = VtBarData() + bar.vtSymbol = symbol + bar.symbol = symbol + bar.open = float(d[2]) + bar.high = float(d[3]) + bar.low = float(d[4]) + bar.close = float(d[5]) + bar.date = str(d[0]) + + tempstr=str(round(float(d[1])*10000)).split(".")[0].zfill(4) + bar.time = tempstr[:2]+":"+tempstr[2:4]+":00" + + bar.datetime = datetime.strptime(bar.date + ' ' + bar.time, '%Y%m%d %H:%M:%S') + bar.volume = d[6] + bar.openInterest = d[7] + flt = {'datetime': bar.datetime} + collection.update_one(flt, {'$set':bar.__dict__}, upsert=True) + print bar.date, bar.time + + print u'插入完毕,耗时:%s' % (time()-start) + #---------------------------------------------------------------------- def loadTdxCsv(fileName, dbName, symbol): diff --git a/vnpy/trader/app/ctaStrategy/ctaTemplate.py b/vnpy/trader/app/ctaStrategy/ctaTemplate.py index 1b23586e..e87b1638 100644 --- a/vnpy/trader/app/ctaStrategy/ctaTemplate.py +++ b/vnpy/trader/app/ctaStrategy/ctaTemplate.py @@ -286,10 +286,10 @@ class TargetPosTemplate(CtaTemplate): # 回测模式下,采用合并平仓和反向开仓委托的方式 if self.getEngineType() == ENGINETYPE_BACKTESTING: if posChange > 0: - vtOrderID = self.buy(longPrice, abs(posChange)) + l = self.buy(longPrice, abs(posChange)) else: - vtOrderID = self.short(shortPrice, abs(posChange)) - self.orderList.append(vtOrderID) + l = self.short(shortPrice, abs(posChange)) + self.orderList.extend(l) # 实盘模式下,首先确保之前的委托都已经结束(全成、撤销) # 然后先发平仓委托,等待成交后,再发送新的开仓委托 @@ -301,16 +301,16 @@ class TargetPosTemplate(CtaTemplate): # 买入 if posChange > 0: if self.pos < 0: - vtOrderID = self.cover(longPrice, abs(self.pos)) + l = self.cover(longPrice, abs(self.pos)) else: - vtOrderID = self.buy(longPrice, abs(posChange)) + l = self.buy(longPrice, abs(posChange)) # 卖出 else: if self.pos > 0: - vtOrderID = self.sell(shortPrice, abs(self.pos)) + l = self.sell(shortPrice, abs(self.pos)) else: - vtOrderID = self.short(shortPrice, abs(posChange)) - self.orderList.append(vtOrderID) + l = self.short(shortPrice, abs(posChange)) + self.orderList.extend(l) ######################################################################## diff --git a/vnpy/trader/app/dataRecorder/drEngine.py b/vnpy/trader/app/dataRecorder/drEngine.py index f8d334bd..198f1f22 100644 --- a/vnpy/trader/app/dataRecorder/drEngine.py +++ b/vnpy/trader/app/dataRecorder/drEngine.py @@ -19,9 +19,10 @@ from vnpy.event import Event from vnpy.trader.vtEvent import * from vnpy.trader.vtFunction import todayDate, getJsonPath from vnpy.trader.vtObject import VtSubscribeReq, VtLogData, VtBarData, VtTickData +from vnpy.trader.app.ctaStrategy.ctaTemplate import BarManager -from vnpy.trader.app.dataRecorder.drBase import * -from vnpy.trader.app.dataRecorder.language import text +from .drBase import * +from .language import text ######################################################################## @@ -44,10 +45,10 @@ class DrEngine(object): self.activeSymbolDict = {} # Tick对象字典 - self.tickDict = {} + self.tickSymbolSet = set() - # K线对象字典 - self.barDict = {} + # K线合成器字典 + self.bmDict = {} # 配置字典 self.settingDict = OrderedDict() @@ -77,11 +78,13 @@ class DrEngine(object): if not working: return + # Tick记录配置 if 'tick' in drSetting: l = drSetting['tick'] for setting in l: symbol = setting[0] + gateway = setting[1] vtSymbol = symbol req = VtSubscribeReq() @@ -97,16 +100,17 @@ class DrEngine(object): req.currency = setting[3] req.productClass = setting[4] - self.mainEngine.subscribe(req, setting[1]) + self.mainEngine.subscribe(req, gateway) - tick = VtTickData() # 该tick实例可以用于缓存部分数据(目前未使用) - self.tickDict[vtSymbol] = tick + #tick = VtTickData() # 该tick实例可以用于缓存部分数据(目前未使用) + #self.tickDict[vtSymbol] = tick + self.tickSymbolSet.add(vtSymbol) # 保存到配置字典中 if vtSymbol not in self.settingDict: d = { 'symbol': symbol, - 'gateway': setting[1], + 'gateway': gateway, 'tick': True } self.settingDict[vtSymbol] = d @@ -114,11 +118,13 @@ class DrEngine(object): d = self.settingDict[vtSymbol] d['tick'] = True + # 分钟线记录配置 if 'bar' in drSetting: l = drSetting['bar'] for setting in l: symbol = setting[0] + gateway = setting[1] vtSymbol = symbol req = VtSubscribeReq() @@ -132,158 +138,85 @@ class DrEngine(object): req.currency = setting[3] req.productClass = setting[4] - self.mainEngine.subscribe(req, setting[1]) - - bar = VtBarData() - self.barDict[vtSymbol] = bar + self.mainEngine.subscribe(req, gateway) # 保存到配置字典中 if vtSymbol not in self.settingDict: d = { 'symbol': symbol, - 'gateway': setting[1], + 'gateway': gateway, 'bar': True } self.settingDict[vtSymbol] = d else: d = self.settingDict[vtSymbol] - d['bar'] = True + d['bar'] = True + + # 创建BarManager对象 + self.bmDict[vtSymbol] = BarManager(self.onBar) + # 主力合约记录配置 if 'active' in drSetting: d = drSetting['active'] - - # 注意这里的vtSymbol对于IB和LTS接口,应该后缀.交易所 - for activeSymbol, vtSymbol in d.items(): - self.activeSymbolDict[vtSymbol] = activeSymbol - - # 保存到配置字典中 - if vtSymbol not in self.settingDict: - d = { - 'symbol': symbol, - 'gateway': setting[1], - 'active': True - } - self.settingDict[vtSymbol] = d - else: - d = self.settingDict[vtSymbol] - d['active'] = True + self.activeSymbolDict = {vtSymbol:activeSymbol for activeSymbol, vtSymbol in d.items()} - ##---------------------------------------------------------------------- - #def loadCsvSetting(self): - #"""加载CSV配置""" - #with open(self.settingFileName) as f: - #drSetting = csv.DictReader(f) - - #for d in drSetting: - ## 读取配置 - #gatewayName = d['gateway'] - #symbol = d['symbol'] - #exchange = d['exchange'] - #currency = d['currency'] - #productClass = d['product'] - #recordTick = d['tick'] - #recordBar = d['bar'] - #activeSymbol = d['active'] - - #if exchange: - #vtSymbol = '.'.join([symbol, exchange]) - #else: - #vtSymbol = symbol - - ## 订阅行情 - #req = VtSubscribeReq() - #req.symbol = symbol - #req.exchange = exchange - #req.currency = currency - #req.productClass = productClass - #self.mainEngine.subscribe(req, gatewayName) - - ## 设置需要记录的数据 - #if recordTick: - #tick = VtTickData() - #self.tickDict[vtSymbol] = VtTickData() - - #if recordBar: - #self.barDict[vtSymbol] = VtBarData() - - #if activeSymbol: - #self.activeSymbolDict[vtSymbol] = activeSymbol - - ## 保存配置到缓存中 - #self.settingDict[vtSymbol] = d - #---------------------------------------------------------------------- def getSetting(self): """获取配置""" - return self.settingDict + return self.settingDict, self.activeSymbolDict #---------------------------------------------------------------------- def procecssTickEvent(self, event): - """处理行情推送""" + """处理行情事件""" tick = event.dict_['data'] vtSymbol = tick.vtSymbol - # 转化Tick格式 + # 生成datetime对象 if not tick.datetime: tick.datetime = datetime.strptime(' '.join([tick.date, tick.time]), '%Y%m%d %H:%M:%S.%f') + + self.onTick(tick) - # 更新Tick数据 - if vtSymbol in self.tickDict: + bm = self.bmDict.get(vtSymbol, None) + if bm: + bm.updateTick(tick) + + #---------------------------------------------------------------------- + def onTick(self, tick): + """Tick更新""" + vtSymbol = tick.vtSymbol + + if vtSymbol in self.tickSymbolSet: self.insertData(TICK_DB_NAME, vtSymbol, tick) if vtSymbol in self.activeSymbolDict: activeSymbol = self.activeSymbolDict[vtSymbol] self.insertData(TICK_DB_NAME, activeSymbol, tick) - # 发出日志 + self.writeDrLog(text.TICK_LOGGING_MESSAGE.format(symbol=tick.vtSymbol, time=tick.time, last=tick.lastPrice, bid=tick.bidPrice1, ask=tick.askPrice1)) - - # 更新分钟线数据 - if vtSymbol in self.barDict: - bar = self.barDict[vtSymbol] - - # 如果第一个TICK或者新的一分钟 - if (not bar.datetime or - bar.datetime.minute != tick.datetime.minute or - bar.datetime.hour != tick.datetime.hour): - if bar.vtSymbol: - newBar = copy.copy(bar) - self.insertData(MINUTE_DB_NAME, vtSymbol, newBar) - - if vtSymbol in self.activeSymbolDict: - activeSymbol = self.activeSymbolDict[vtSymbol] - self.insertData(MINUTE_DB_NAME, activeSymbol, newBar) - - self.writeDrLog(text.BAR_LOGGING_MESSAGE.format(symbol=bar.vtSymbol, - time=bar.time, - open=bar.open, - high=bar.high, - low=bar.low, - close=bar.close)) - - bar.vtSymbol = tick.vtSymbol - bar.symbol = tick.symbol - bar.exchange = tick.exchange - - bar.open = tick.lastPrice - bar.high = tick.lastPrice - bar.low = tick.lastPrice - bar.close = tick.lastPrice - - bar.date = tick.date - bar.time = tick.time - bar.datetime = tick.datetime.replace(second=0, microsecond=0) - bar.volume = tick.volume - bar.openInterest = tick.openInterest - # 否则继续累加新的K线 - else: - bar.high = max(bar.high, tick.lastPrice) - bar.low = min(bar.low, tick.lastPrice) - bar.close = tick.lastPrice + + #---------------------------------------------------------------------- + def onBar(self, bar): + """分钟线更新""" + vtSymbol = bar.vtSymbol + + self.insertData(MINUTE_DB_NAME, vtSymbol, bar) + + if vtSymbol in self.activeSymbolDict: + activeSymbol = self.activeSymbolDict[vtSymbol] + self.insertData(MINUTE_DB_NAME, activeSymbol, bar) + + self.writeDrLog(text.BAR_LOGGING_MESSAGE.format(symbol=bar.vtSymbol, + time=bar.time, + open=bar.open, + high=bar.high, + low=bar.low, + close=bar.close)) #---------------------------------------------------------------------- def registerEvent(self): diff --git a/vnpy/trader/app/dataRecorder/uiDrWidget.py b/vnpy/trader/app/dataRecorder/uiDrWidget.py index 78a898fc..42f5ee0e 100644 --- a/vnpy/trader/app/dataRecorder/uiDrWidget.py +++ b/vnpy/trader/app/dataRecorder/uiDrWidget.py @@ -115,7 +115,7 @@ class DrEngineManager(QtWidgets.QWidget): #---------------------------------------------------------------------- def updateSetting(self): """显示引擎行情记录配置""" - setting = self.drEngine.getSetting() + setting, activeSetting = self.drEngine.getSetting() for d in setting.values(): if 'tick' in d and d['tick']: @@ -128,14 +128,14 @@ class DrEngineManager(QtWidgets.QWidget): self.barTable.setItem(0, 0, TableCell(d['symbol'])) self.barTable.setItem(0, 1, TableCell(d['gateway'])) - if 'active'in d and d['active']: - self.activeTable.insertRow(0) - self.activeTable.setItem(0, 0, TableCell(d['active'])) - self.activeTable.setItem(0, 1, TableCell(d['symbol'])) + for vtSymbol, activeSymbol in activeSetting.items(): + self.activeTable.insertRow(0) + self.activeTable.setItem(0, 0, TableCell(activeSymbol)) + self.activeTable.setItem(0, 1, TableCell(vtSymbol)) - self.tickTable.resizeColumnsToContents() - self.barTable.resizeColumnsToContents() - self.activeTable.resizeColumnsToContents() + self.tickTable.resizeColumnsToContents() + self.barTable.resizeColumnsToContents() + self.activeTable.resizeColumnsToContents() diff --git a/vnpy/trader/app/spreadTrading/stEngine.py b/vnpy/trader/app/spreadTrading/stEngine.py index 808bf565..f6d63347 100644 --- a/vnpy/trader/app/spreadTrading/stEngine.py +++ b/vnpy/trader/app/spreadTrading/stEngine.py @@ -356,6 +356,7 @@ class StAlgoEngine(object): req = VtOrderReq() req.symbol = contract.symbol req.exchange = contract.exchange + req.vtSymbol = contract.vtSymbol req.direction = direction req.offset = offset req.volume = int(volume) diff --git a/vnpy/trader/language/chinese/constant.py b/vnpy/trader/language/chinese/constant.py index 53f53741..1aadb9b4 100644 --- a/vnpy/trader/language/chinese/constant.py +++ b/vnpy/trader/language/chinese/constant.py @@ -41,6 +41,9 @@ PRODUCT_FOREX = u'外汇' PRODUCT_UNKNOWN = u'未知' PRODUCT_SPOT = u'现货' PRODUCT_DEFER = u'延期' +PRODUCT_ETF = u'ETF' +PRODUCT_WARRANT = u'权证' +PRODUCT_BOND = u'债券' PRODUCT_NONE = '' # 价格类型常量 diff --git a/vnpy/trader/uiBasicWidget.py b/vnpy/trader/uiBasicWidget.py index c5c0979f..29376662 100644 --- a/vnpy/trader/uiBasicWidget.py +++ b/vnpy/trader/uiBasicWidget.py @@ -1046,6 +1046,7 @@ class TradingWidget(QtWidgets.QFrame): req = VtOrderReq() req.symbol = symbol req.exchange = exchange + req.vtSymbol = contract.vtSymbol req.price = self.spinPrice.value() req.volume = self.spinVolume.value() req.direction = unicode(self.comboDirection.currentText()) diff --git a/vnpy/trader/vtEngine.py b/vnpy/trader/vtEngine.py index d343ae35..598e8bb1 100644 --- a/vnpy/trader/vtEngine.py +++ b/vnpy/trader/vtEngine.py @@ -477,8 +477,10 @@ class DataEngine(object): contract = self.getContract(vtSymbol) if contract: + detail.exchange = contract.exchange + # 上期所合约 - if contract.exchange is EXCHANGE_SHFE: + if contract.exchange == EXCHANGE_SHFE: detail.mode = detail.MODE_SHFE # 检查是否有平今惩罚 @@ -491,10 +493,7 @@ class DataEngine(object): #---------------------------------------------------------------------- def updateOrderReq(self, req, vtOrderID): """委托请求更新""" - if req.exchange: - vtSymbol = '.'.join([req.symbol, req.exchange]) - else: - vtSymbol = req.symbol + vtSymbol = req.vtSymbol detail = self.getPositionDetail(vtSymbol) detail.updateOrderReq(req, vtOrderID) @@ -519,6 +518,16 @@ class LogEngine(object): LEVEL_WARN = logging.WARN LEVEL_ERROR = logging.ERROR LEVEL_CRITICAL = logging.CRITICAL + + # 单例对象 + instance = None + + #---------------------------------------------------------------------- + def __new__(cls, *args, **kwargs): + """创建对象,保证单例""" + if not cls.instance: + cls.instance = super(LogEngine, cls).__new__(cls, *args, **kwargs) + return cls.instance #---------------------------------------------------------------------- def __init__(self): @@ -637,6 +646,7 @@ class PositionDetail(object): self.shortTdFrozen = EMPTY_INT self.mode = self.MODE_NORMAL + self.exchange = EMPTY_STRING self.workingOrderDict = {} @@ -654,14 +664,18 @@ class PositionDetail(object): # 平昨 elif trade.offset is OFFSET_CLOSEYESTERDAY: self.shortYd -= trade.volume - # 平仓(非上期所,优先平今) + # 平仓 elif trade.offset is OFFSET_CLOSE: - self.shortTd -= trade.volume - - if self.shortTd < 0: - self.shortYd += self.shortTd - self.shortTd = 0 - + # 上期所等同于平昨 + if self.exchange is EXCHANGE_SHFE: + self.shortYd -= trade.volume + # 非上期所,优先平今 + else: + self.shortTd -= trade.volume + + if self.shortTd < 0: + self.shortYd += self.shortTd + self.shortTd = 0 # 空头 elif trade.direction is DIRECTION_SHORT: # 开仓 @@ -675,11 +689,16 @@ class PositionDetail(object): self.longYd -= trade.volume # 平仓 elif trade.offset is OFFSET_CLOSE: - self.longTd -= trade.volume - - if self.longTd < 0: - self.longYd += self.longTd - self.longTd = 0 + # 上期所等同于平昨 + if self.exchange is EXCHANGE_SHFE: + self.longYd -= trade.volume + # 非上期所,优先平今 + else: + self.longTd -= trade.volume + + if self.longTd < 0: + self.longYd += self.longTd + self.longTd = 0 # 汇总今昨 self.calculatePosition() @@ -710,14 +729,13 @@ class PositionDetail(object): self.shortPos = pos.position self.shortYd = pos.ydPosition self.shortTd = self.shortPos - self.shortYd + + self.output() #---------------------------------------------------------------------- def updateOrderReq(self, req, vtOrderID): """发单更新""" - if req.exchange: - vtSymbol = '.'.join([req.symbol, req.exchange]) - else: - vtSymbol = req.symbol + vtSymbol = req.vtSymbol # 基于请求生成委托对象 order = VtOrderData() @@ -867,12 +885,18 @@ class PositionDetail(object): return [req] # 如果平仓量小于昨可用,全部平昨 elif req.volume <= ydAvailable: - req.offset = OFFSET_CLOSE # OFFSET_CLOSE在上期所等于平昨 + if self.exchange is EXCHANGE_SHFE: + req.offset = OFFSET_CLOSEYESTERDAY + else: + req.offset = OFFSET_CLOSE return [req] # 平仓量大于昨可用,平仓再反向开仓 else: reqClose = copy(req) - reqClose.offset = OFFSET_CLOSE + if self.exchange is EXCHANGE_SHFE: + req.offset = OFFSET_CLOSEYESTERDAY + else: + req.offset = OFFSET_CLOSE reqClose.volume = ydAvailable reqOpen = copy(req) diff --git a/vnpy/trader/vtObject.py b/vnpy/trader/vtObject.py index e5d86f08..a0881b7a 100644 --- a/vnpy/trader/vtObject.py +++ b/vnpy/trader/vtObject.py @@ -292,6 +292,7 @@ class VtOrderReq(object): """Constructor""" self.symbol = EMPTY_STRING # 代码 self.exchange = EMPTY_STRING # 交易所 + self.vtSymbol = EMPTY_STRING # VT合约代码 self.price = EMPTY_FLOAT # 价格 self.volume = EMPTY_INT # 数量 @@ -318,6 +319,7 @@ class VtCancelOrderReq(object): """Constructor""" self.symbol = EMPTY_STRING # 代码 self.exchange = EMPTY_STRING # 交易所 + self.vtSymbol = EMPTY_STRING # VT合约代码 # 以下字段主要和CTP、LTS类接口相关 self.orderID = EMPTY_STRING # 报单号