diff --git a/README-en.md b/README-en.md index 95bdd77f..a808f598 100644 --- a/README-en.md +++ b/README-en.md @@ -37,7 +37,8 @@ Using the vn.py project, institutional investors and professional traders, such - LTS(vn.lts) - QDP(vn.qdp) - + + - CSHSHLP(vn.cshshlp) **Chinese Precious Metal Market** diff --git a/vnpy/api/datayes/README.md b/archive/datayes/README.md similarity index 100% rename from vnpy/api/datayes/README.md rename to archive/datayes/README.md diff --git a/vnpy/api/datayes/__init__.py b/archive/datayes/__init__.py similarity index 100% rename from vnpy/api/datayes/__init__.py rename to archive/datayes/__init__.py diff --git a/vnpy/api/datayes/api.py b/archive/datayes/api.py similarity index 100% rename from vnpy/api/datayes/api.py rename to archive/datayes/api.py diff --git a/vnpy/api/datayes/config/db_EQU_D1.csv b/archive/datayes/config/db_EQU_D1.csv similarity index 100% rename from vnpy/api/datayes/config/db_EQU_D1.csv rename to archive/datayes/config/db_EQU_D1.csv diff --git a/vnpy/api/datayes/download.sh b/archive/datayes/download.sh similarity index 100% rename from vnpy/api/datayes/download.sh rename to archive/datayes/download.sh diff --git a/vnpy/api/datayes/errors.py b/archive/datayes/errors.py similarity index 100% rename from vnpy/api/datayes/errors.py rename to archive/datayes/errors.py diff --git a/vnpy/api/datayes/fun/fetch.R b/archive/datayes/fun/fetch.R similarity index 100% rename from vnpy/api/datayes/fun/fetch.R rename to archive/datayes/fun/fetch.R diff --git a/vnpy/api/datayes/names/equTicker.json b/archive/datayes/names/equTicker.json similarity index 100% rename from vnpy/api/datayes/names/equTicker.json rename to archive/datayes/names/equTicker.json diff --git a/vnpy/api/datayes/names/fudTicker.json b/archive/datayes/names/fudTicker.json similarity index 100% rename from vnpy/api/datayes/names/fudTicker.json rename to archive/datayes/names/fudTicker.json diff --git a/vnpy/api/datayes/names/futTicker.json b/archive/datayes/names/futTicker.json similarity index 100% rename from vnpy/api/datayes/names/futTicker.json rename to archive/datayes/names/futTicker.json diff --git a/vnpy/api/datayes/names/idxTicker.json b/archive/datayes/names/idxTicker.json similarity index 100% rename from vnpy/api/datayes/names/idxTicker.json rename to archive/datayes/names/idxTicker.json diff --git a/vnpy/api/datayes/names/optTicker.json b/archive/datayes/names/optTicker.json similarity index 100% rename from vnpy/api/datayes/names/optTicker.json rename to archive/datayes/names/optTicker.json diff --git a/vnpy/api/datayes/names/secID.json b/archive/datayes/names/secID.json similarity index 100% rename from vnpy/api/datayes/names/secID.json rename to archive/datayes/names/secID.json diff --git a/vnpy/api/datayes/prepare.sh b/archive/datayes/prepare.sh similarity index 100% rename from vnpy/api/datayes/prepare.sh rename to archive/datayes/prepare.sh diff --git a/vnpy/api/datayes/static/figs/fig1.png b/archive/datayes/static/figs/fig1.png similarity index 100% rename from vnpy/api/datayes/static/figs/fig1.png rename to archive/datayes/static/figs/fig1.png diff --git a/vnpy/api/datayes/static/figs/fig2.png b/archive/datayes/static/figs/fig2.png similarity index 100% rename from vnpy/api/datayes/static/figs/fig2.png rename to archive/datayes/static/figs/fig2.png diff --git a/vnpy/api/datayes/static/figs/fig3.png b/archive/datayes/static/figs/fig3.png similarity index 100% rename from vnpy/api/datayes/static/figs/fig3.png rename to archive/datayes/static/figs/fig3.png diff --git a/vnpy/api/datayes/static/figs/fig4.png b/archive/datayes/static/figs/fig4.png similarity index 100% rename from vnpy/api/datayes/static/figs/fig4.png rename to archive/datayes/static/figs/fig4.png diff --git a/vnpy/api/datayes/static/figs/fig5.png b/archive/datayes/static/figs/fig5.png similarity index 100% rename from vnpy/api/datayes/static/figs/fig5.png rename to archive/datayes/static/figs/fig5.png diff --git a/vnpy/api/datayes/static/figs/fig6.png b/archive/datayes/static/figs/fig6.png similarity index 100% rename from vnpy/api/datayes/static/figs/fig6.png rename to archive/datayes/static/figs/fig6.png diff --git a/vnpy/api/datayes/static/tutorial.ipynb b/archive/datayes/static/tutorial.ipynb similarity index 100% rename from vnpy/api/datayes/static/tutorial.ipynb rename to archive/datayes/static/tutorial.ipynb diff --git a/vnpy/api/datayes/static/tutorial.md b/archive/datayes/static/tutorial.md similarity index 100% rename from vnpy/api/datayes/static/tutorial.md rename to archive/datayes/static/tutorial.md diff --git a/vnpy/api/datayes/storage.py b/archive/datayes/storage.py similarity index 100% rename from vnpy/api/datayes/storage.py rename to archive/datayes/storage.py diff --git a/vnpy/api/datayes/tests.py b/archive/datayes/tests.py similarity index 100% rename from vnpy/api/datayes/tests.py rename to archive/datayes/tests.py diff --git a/vnpy/api/datayes/update.sh b/archive/datayes/update.sh similarity index 100% rename from vnpy/api/datayes/update.sh rename to archive/datayes/update.sh diff --git a/docker/Dockerfile b/docker/Dockerfile index d6ba79e6..fa6177eb 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -61,7 +61,7 @@ RUN echo "开始配置系vnpy环境" \ && echo "更改 pip 源" \ && mkdir ~/.pip \ && echo "[global]" >> ~/.pip/pip.conf \ - && echo "index-url = http://pypi.douban.com/simple" >> ~/.pip/pip.conf \ + && echo "index-url = https://pypi.doubanio.com/simple/" >> ~/.pip/pip.conf \ && echo "使用 pip 安装 python 库" \ && pip install TA-Lib \ && conda clean -ay \ @@ -70,4 +70,4 @@ RUN echo "开始配置系vnpy环境" \ WORKDIR /srv/vnpy # 暂时不设置入口点,否则不能使用 -it 交互模式 -# ENTRYPOINT python /srv/vnpy/vn.trader/vtServer.py \ No newline at end of file +# ENTRYPOINT python /srv/vnpy/vn.trader/vtServer.py diff --git a/examples/CtaBacktesting/backtesting.ipynb b/examples/CtaBacktesting/backtesting.ipynb index 1de18e1d..0540c100 100644 --- a/examples/CtaBacktesting/backtesting.ipynb +++ b/examples/CtaBacktesting/backtesting.ipynb @@ -79,13 +79,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "2017-06-02 16:07:31.265000\t开始载入数据\n", - "2017-06-02 16:07:31.423000\t载入完成,数据量:331890\n", - "2017-06-02 16:07:31.423000\t开始回测\n", - "2017-06-02 16:07:31.441000\t策略初始化完成\n", - "2017-06-02 16:07:31.441000\t策略启动完成\n", - "2017-06-02 16:07:31.442000\t开始回放数据\n", - "2017-06-02 16:07:54.738000\t数据回放结束\n" + "2017-07-06 23:15:17.471000\t开始载入数据\n", + "2017-07-06 23:15:17.485000\t载入完成,数据量:0\n", + "2017-07-06 23:15:17.485000\t开始回测\n", + "2017-07-06 23:15:17.485000\t策略初始化完成\n", + "2017-07-06 23:15:17.485000\t策略启动完成\n", + "2017-07-06 23:15:17.485000\t开始回放数据\n", + "2017-07-06 23:15:17.486000\t数据回放结束\n" ] } ], diff --git a/examples/CtaTrading/CTA_setting.json b/examples/CtaTrading/CTA_setting.json new file mode 100644 index 00000000..19884be3 --- /dev/null +++ b/examples/CtaTrading/CTA_setting.json @@ -0,0 +1,19 @@ +[ + { + "name": "double ema", + "className": "EmaDemoStrategy", + "vtSymbol": "IF1706" + }, + + { + "name": "atr rsi", + "className": "AtrRsiStrategy", + "vtSymbol": "IC1706" + }, + + { + "name": "king keltner", + "className": "KkStrategy", + "vtSymbol": "IH1706" + } +] \ No newline at end of file diff --git a/examples/CtaTrading/CTP_connect.json b/examples/CtaTrading/CTP_connect.json new file mode 100644 index 00000000..e4bfa6b8 --- /dev/null +++ b/examples/CtaTrading/CTP_connect.json @@ -0,0 +1,7 @@ +{ + "brokerID": "9999", + "mdAddress": "tcp://180.168.146.187:10011", + "tdAddress": "tcp://180.168.146.187:10001", + "userID": "simnow申请", + "password": "simnow申请" +} \ No newline at end of file diff --git a/examples/CtaTrading/runCtaTrading.py b/examples/CtaTrading/runCtaTrading.py new file mode 100644 index 00000000..bd5eb84b --- /dev/null +++ b/examples/CtaTrading/runCtaTrading.py @@ -0,0 +1,119 @@ +# encoding: UTF-8 + +import multiprocessing +from time import sleep +from datetime import datetime, time + +from vnpy.event import EventEngine2 +from vnpy.trader.vtEvent import EVENT_LOG +from vnpy.trader.vtEngine import MainEngine +from vnpy.trader.gateway import ctpGateway +from vnpy.trader.app import ctaStrategy +from vnpy.trader.app.ctaStrategy.ctaBase import EVENT_CTA_LOG + +#---------------------------------------------------------------------- +def printLog(content): + """输出日志""" + t = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + print '%s\t%s' %(t, content) + +#---------------------------------------------------------------------- +def processLogEvent(event): + """处理日志事件""" + log = event.dict_['data'] + if log.gatewayName: + content = '%s:%s' %(log.gatewayName, log.logContent) + else: + content = '%s:%s' %('MainEngine', log.logContent) + printLog(content) + +#---------------------------------------------------------------------- +def processCtaLogEvent(event): + """处理CTA模块日志事件""" + log = event.dict_['data'] + content = '%s:%s' %('CTA Engine', log.logContent) + printLog(content) + +#---------------------------------------------------------------------- +def runChildProcess(): + """子进程运行函数""" + print '-'*20 + printLog(u'启动CTA策略运行子进程') + + ee = EventEngine2() + printLog(u'事件引擎创建成功') + + me = MainEngine(ee) + me.addGateway(ctpGateway) + me.addApp(ctaStrategy) + printLog(u'主引擎创建成功') + + ee.register(EVENT_LOG, processLogEvent) + ee.register(EVENT_CTA_LOG, processCtaLogEvent) + printLog(u'注册日志事件监听') + + me.connect('CTP') + printLog(u'连接CTP接口') + + sleep(5) # 等待CTP接口初始化 + + cta = me.appDict[ctaStrategy.appName] + + cta.loadSetting() + printLog(u'CTA策略载入成功') + + cta.initAll() + printLog(u'CTA策略初始化成功') + + cta.startAll() + printLog(u'CTA策略启动成功') + + while True: + sleep(1) + +#---------------------------------------------------------------------- +def runParentProcess(): + """父进程运行函数""" + printLog(u'启动CTA策略守护父进程') + + DAY_START = time(8, 45) # 日盘启动和停止时间 + DAY_END = time(15, 30) + + NIGHT_START = time(20, 45) # 夜盘启动和停止时间 + NIGHT_END = time(2, 45) + + p = None # 子进程句柄 + + while True: + currentTime = datetime.now().time() + recording = False + + # 判断当前处于的时间段 + if ((currentTime >= DAY_START and currentTime <= DAY_END) or + (currentTime >= NIGHT_START) or + (currentTime <= NIGHT_END)): + recording = True + + # 记录时间则需要启动子进程 + if recording and p is None: + printLog(u'启动子进程') + p = multiprocessing.Process(target=runChildProcess) + p.start() + printLog(u'子进程启动成功') + + # 非记录时间则退出子进程 + if not recording and p is not None: + printLog(u'关闭子进程') + p.terminate() + p.join() + p = None + printLog(u'子进程关闭成功') + + sleep(5) + + +if __name__ == '__main__': + runChildProcess() + + # 尽管同样实现了无人值守,但强烈建议每天启动时人工检查,为自己的PNL负责 + # runParentProcess() \ No newline at end of file diff --git a/examples/DataRecording/CTP_connect.json b/examples/DataRecording/CTP_connect.json new file mode 100644 index 00000000..e4bfa6b8 --- /dev/null +++ b/examples/DataRecording/CTP_connect.json @@ -0,0 +1,7 @@ +{ + "brokerID": "9999", + "mdAddress": "tcp://180.168.146.187:10011", + "tdAddress": "tcp://180.168.146.187:10001", + "userID": "simnow申请", + "password": "simnow申请" +} \ No newline at end of file diff --git a/examples/DataRecording/DR_setting.json b/examples/DataRecording/DR_setting.json new file mode 100644 index 00000000..07030299 --- /dev/null +++ b/examples/DataRecording/DR_setting.json @@ -0,0 +1,18 @@ +{ + "working": true, + + "tick": + [ + ], + + "bar": + [ + ["BTC_CNY_SPOT", "OKCOIN"], + ["LTC_CNY_SPOT", "OKCOIN"], + ["ETH_CNY_SPOT", "OKCOIN"] + ], + + "active": + { + } +} \ No newline at end of file diff --git a/examples/DataRecording/runDataRecording.py b/examples/DataRecording/runDataRecording.py new file mode 100644 index 00000000..c05425ea --- /dev/null +++ b/examples/DataRecording/runDataRecording.py @@ -0,0 +1,95 @@ +# encoding: UTF-8 + +import multiprocessing +from time import sleep +from datetime import datetime, time + +from vnpy.event import EventEngine2 +from vnpy.trader.vtEvent import EVENT_LOG +from vnpy.trader.vtEngine import MainEngine +from vnpy.trader.gateway import ctpGateway +from vnpy.trader.app import dataRecorder + +#---------------------------------------------------------------------- +def printLog(content): + """输出日志""" + t = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + print '%s\t%s' %(t, content) + +#---------------------------------------------------------------------- +def processLogEvent(event): + """处理日志事件""" + log = event.dict_['data'] + if log.gatewayName: + content = '%s:%s' %(log.gatewayName, log.logContent) + else: + content = '%s:%s' %('MainEngine', log.logContent) + printLog(content) + +#---------------------------------------------------------------------- +def runChildProcess(): + """子进程运行函数""" + print '-'*20 + printLog(u'启动行情记录运行子进程') + + ee = EventEngine2() + printLog(u'事件引擎创建成功') + + me = MainEngine(ee) + me.addGateway(ctpGateway) + me.addApp(dataRecorder) + printLog(u'主引擎创建成功') + + ee.register(EVENT_LOG, processLogEvent) + printLog(u'注册日志事件监听') + + me.connect('CTP') + printLog(u'连接CTP接口') + + while True: + sleep(1) + +#---------------------------------------------------------------------- +def runParentProcess(): + """父进程运行函数""" + printLog(u'启动行情记录守护父进程') + + DAY_START = time(8, 57) # 日盘启动和停止时间 + DAY_END = time(15, 18) + + NIGHT_START = time(20, 57) # 夜盘启动和停止时间 + NIGHT_END = time(2, 33) + + p = None # 子进程句柄 + + while True: + currentTime = datetime.now().time() + recording = False + + # 判断当前处于的时间段 + if ((currentTime >= DAY_START and currentTime <= DAY_END) or + (currentTime >= NIGHT_START) or + (currentTime <= NIGHT_END)): + recording = True + + # 记录时间则需要启动子进程 + if recording and p is None: + printLog(u'启动子进程') + p = multiprocessing.Process(target=runChildProcess) + p.start() + printLog(u'子进程启动成功') + + # 非记录时间则退出子进程 + if not recording and p is not None: + printLog(u'关闭子进程') + p.terminate() + p.join() + p = None + printLog(u'子进程关闭成功') + + sleep(5) + + +if __name__ == '__main__': + runChildProcess() + #runParentProcess() \ No newline at end of file diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..c95316f3 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,11 @@ +# vn.py项目的应用示例 + +本文件夹中的内容主要是关于如何在交易业务中使用vn.py的示例 + +* VnTrader:最常用的vn.py图形交易界面 + +* DataRecording:全自动行情记录工具(无需用户每日定时重启) + +* CtaTrading:无图形界面模式的CTA策略交易 + +* CtaBacktesting:CTA策略的回测和优化 \ No newline at end of file diff --git a/examples/ShcifcoDataService/config.json b/examples/ShcifcoDataService/config.json new file mode 100644 index 00000000..5d7715c9 --- /dev/null +++ b/examples/ShcifcoDataService/config.json @@ -0,0 +1,11 @@ +{ + "MONGO_HOST": "localhost", + "MONGO_PORT": 27017, + + "SHCIFCO_IP": "180.169.126.123", + "SHCIFCO_PORT": "45065", + "SHCIFCO_TOKEN": "请联系上海中期申请", + + "SYMBOLS": ["cu1707", "cu1708", "cu1709", "cu1712", + "m1707", "m1708", "m1709", "m1712"] +} \ No newline at end of file diff --git a/examples/ShcifcoDataService/dataService.py b/examples/ShcifcoDataService/dataService.py new file mode 100644 index 00000000..270cbde9 --- /dev/null +++ b/examples/ShcifcoDataService/dataService.py @@ -0,0 +1,91 @@ +# encoding: UTF-8 + +import json +import time +import datetime +import random + +from pymongo import MongoClient, ASCENDING + +from vnpy.data.shcifco.vnshcifco import ShcifcoApi, PERIOD_1MIN +from vnpy.trader.vtObject import VtBarData +from vnpy.trader.app.ctaStrategy.ctaBase import MINUTE_DB_NAME + + +# 加载配置 +config = open('config.json') +setting = json.load(config) + +MONGO_HOST = setting['MONGO_HOST'] +MONGO_PORT = setting['MONGO_PORT'] +SHCIFCO_IP = setting['SHCIFCO_IP'] +SHCIFCO_PORT = setting['SHCIFCO_PORT'] +SHCIFCO_TOKEN = setting['SHCIFCO_TOKEN'] +SYMBOLS = setting['SYMBOLS'] + +api = ShcifcoApi(SHCIFCO_IP, SHCIFCO_PORT, SHCIFCO_TOKEN) # 历史行情服务API对象 +mc = MongoClient(MONGO_HOST, MONGO_PORT) # Mongo连接 +db = mc[MINUTE_DB_NAME] # 数据库 + + +#---------------------------------------------------------------------- +def generateVtBar(d): + """生成K线""" + bar = VtBarData() + + bar.symbol = d['symbol'] + bar.vtSymbol = d['symbol'] + bar.date = d['date'] + bar.time = ':'.join([d['time'][:2], d['time'][2:]]) + bar.open = d['open'] + bar.high = d['high'] + bar.low = d['low'] + bar.close = d['close'] + bar.volume = d['volume'] + bar.openInterest = d['openInterest'] + bar.datetime = datetime.datetime.strptime(' '.join([bar.date, bar.time]), '%Y%m%d %H:%M') + + return bar + +#---------------------------------------------------------------------- +def downMinuteBarBySymbol(symbol, num): + """下载某一合约的分钟线数据""" + start = time.time() + + cl = db[symbol] # 集合 + cl.ensure_index([('datetime', ASCENDING)], unique=True) # 添加索引 + + l = api.getHisBar(symbol, num, period=PERIOD_1MIN) + if not l: + print u'%s数据下载失败' %symbol + return + + for d in l: + bar = generateVtBar(d) + d = bar.__dict__ + flt = {'datetime': bar.datetime} + cl.replace_one(flt, d, True) + + end = time.time() + cost = (end - start) * 1000 + + print u'合约%s数据下载完成%s - %s,耗时%s毫秒' %(symbol, generateVtBar(l[0]).datetime, + generateVtBar(l[-1]).datetime, cost) + +#---------------------------------------------------------------------- +def downloadAllMinuteBar(num): + """下载所有配置中的合约的分钟线数据""" + print '-' * 50 + print u'开始下载合约分钟线数据' + print '-' * 50 + + for symbol in SYMBOLS: + downMinuteBarBySymbol(symbol, num) + time.sleep(1) + + print '-' * 50 + print u'合约分钟线数据下载完成' + print '-' * 50 + + + \ No newline at end of file diff --git a/examples/ShcifcoDataService/downloadData.py b/examples/ShcifcoDataService/downloadData.py new file mode 100644 index 00000000..3cf6f3cc --- /dev/null +++ b/examples/ShcifcoDataService/downloadData.py @@ -0,0 +1,11 @@ +# encoding: UTF-8 + +""" +立即下载数据到数据库中,用于手动执行更新操作。 +""" + +from dataService import * + + +if __name__ == '__main__': + downloadAllMinuteBar(1000) \ No newline at end of file diff --git a/examples/ShcifcoDataService/runService.py b/examples/ShcifcoDataService/runService.py new file mode 100644 index 00000000..2b5ac936 --- /dev/null +++ b/examples/ShcifcoDataService/runService.py @@ -0,0 +1,30 @@ +# encoding: UTF-8 + +""" +定时服务,可无人值守运行,实现每日自动下载更新历史行情数据到数据库中。 +""" + +from dataService import * + + +if __name__ == '__main__': + taskCompletedDate = None + + # 生成一个随机的任务下载时间,用于避免所有用户在同一时间访问数据服务器 + taskTime = datetime.time(hour=17, minute=random.randint(1,59)) + + # 进入主循环 + while True: + t = datetime.datetime.now() + + # 每天到达任务下载时间后,执行数据下载的操作 + if t.time() > taskTime and (taskCompletedDate is None or t.date() != taskCompletedDate): + # 下载1000根分钟线数据,足以覆盖过去两天的行情 + downloadAllMinuteBar(1000) + + # 更新任务完成的日期 + taskCompletedDate = t.date() + else: + print u'当前时间%s,任务定时%s' %(t, taskTime) + + time.sleep(60) \ No newline at end of file diff --git a/examples/VnTrader/CTA_setting.json b/examples/VnTrader/CTA_setting.json new file mode 100644 index 00000000..19884be3 --- /dev/null +++ b/examples/VnTrader/CTA_setting.json @@ -0,0 +1,19 @@ +[ + { + "name": "double ema", + "className": "EmaDemoStrategy", + "vtSymbol": "IF1706" + }, + + { + "name": "atr rsi", + "className": "AtrRsiStrategy", + "vtSymbol": "IC1706" + }, + + { + "name": "king keltner", + "className": "KkStrategy", + "vtSymbol": "IH1706" + } +] \ No newline at end of file diff --git a/examples/VnTrader/CTP_connect.json b/examples/VnTrader/CTP_connect.json new file mode 100644 index 00000000..e4bfa6b8 --- /dev/null +++ b/examples/VnTrader/CTP_connect.json @@ -0,0 +1,7 @@ +{ + "brokerID": "9999", + "mdAddress": "tcp://180.168.146.187:10011", + "tdAddress": "tcp://180.168.146.187:10001", + "userID": "simnow申请", + "password": "simnow申请" +} \ No newline at end of file diff --git a/examples/VnTrader/DR_setting.json b/examples/VnTrader/DR_setting.json new file mode 100644 index 00000000..07030299 --- /dev/null +++ b/examples/VnTrader/DR_setting.json @@ -0,0 +1,18 @@ +{ + "working": true, + + "tick": + [ + ], + + "bar": + [ + ["BTC_CNY_SPOT", "OKCOIN"], + ["LTC_CNY_SPOT", "OKCOIN"], + ["ETH_CNY_SPOT", "OKCOIN"] + ], + + "active": + { + } +} \ No newline at end of file diff --git a/examples/VnTrader/FEMAS_connect.json b/examples/VnTrader/FEMAS_connect.json new file mode 100644 index 00000000..e2bb938e --- /dev/null +++ b/examples/VnTrader/FEMAS_connect.json @@ -0,0 +1,7 @@ +{ + "brokerID": "0110", + "tdAddress": "tcp://118.126.16.229:17111", + "password": "飞马账户请自行联系期货公司申请", + "mdAddress": "tcp://118.126.16.229:17101", + "userID": "飞马账户请自行联系期货公司申请" +} \ No newline at end of file diff --git a/examples/VnTrader/HUOBI_connect.json b/examples/VnTrader/HUOBI_connect.json new file mode 100644 index 00000000..3c427281 --- /dev/null +++ b/examples/VnTrader/HUOBI_connect.json @@ -0,0 +1,7 @@ +{ + "accessKey": "火币网站申请", + "secretKey": "火币网站申请", + "interval": 0.5, + "market": "cny", + "debug": false +} \ No newline at end of file diff --git a/examples/VnTrader/IB_connect.json b/examples/VnTrader/IB_connect.json new file mode 100644 index 00000000..1e62cab3 --- /dev/null +++ b/examples/VnTrader/IB_connect.json @@ -0,0 +1,6 @@ +{ + "host": "localhost", + "port": 7497, + "clientId": 888, + "accountCode": "DU545254" +} \ No newline at end of file diff --git a/examples/VnTrader/OANDA_connect.json b/examples/VnTrader/OANDA_connect.json new file mode 100644 index 00000000..21dd2cd9 --- /dev/null +++ b/examples/VnTrader/OANDA_connect.json @@ -0,0 +1,5 @@ +{ + "token": "请在OANDA网站申请", + "accountId": "请在OANDA网站申请", + "settingName": "practice" +} \ No newline at end of file diff --git a/examples/VnTrader/OKCOIN_connect.json b/examples/VnTrader/OKCOIN_connect.json new file mode 100644 index 00000000..8240e55e --- /dev/null +++ b/examples/VnTrader/OKCOIN_connect.json @@ -0,0 +1,7 @@ +{ + "host": "CNY", + "apiKey": "OKCOIN网站申请", + "secretKey": "OKCOIN网站申请", + "trace": false, + "leverage": 20 +} \ No newline at end of file diff --git a/examples/VnTrader/RM_setting.json b/examples/VnTrader/RM_setting.json new file mode 100644 index 00000000..25c98921 --- /dev/null +++ b/examples/VnTrader/RM_setting.json @@ -0,0 +1,9 @@ +{ + "orderFlowClear": 1, + "orderCancelLimit": 10, + "workingOrderLimit": 20, + "tradeLimit": 1000, + "orderSizeLimit": 100, + "active": true, + "orderFlowLimit": 50 +} \ No newline at end of file diff --git a/examples/VnTrader/SGIT_connect.json b/examples/VnTrader/SGIT_connect.json new file mode 100644 index 00000000..a6925448 --- /dev/null +++ b/examples/VnTrader/SGIT_connect.json @@ -0,0 +1,7 @@ +{ + "brokerID": "9999", + "tdAddress": "tcp://140.206.81.6:37776", + "password": "888888", + "mdAddress": "tcp://140.206.81.6:37777", + "userID": "0600035" +} \ No newline at end of file diff --git a/examples/VnTrader/SHZD_connect.json b/examples/VnTrader/SHZD_connect.json new file mode 100644 index 00000000..0bf3746e --- /dev/null +++ b/examples/VnTrader/SHZD_connect.json @@ -0,0 +1,8 @@ +{ + "frontAddress": "222.73.119.230", + "frontPort": 7003, + "marketAddress": "222.73.119.230", + "marketPort": 9003, + "userId": "demo000604", + "userPwd": "888888" +} \ No newline at end of file diff --git a/examples/VnTrader/ST_setting.json b/examples/VnTrader/ST_setting.json new file mode 100644 index 00000000..ba2c1da2 --- /dev/null +++ b/examples/VnTrader/ST_setting.json @@ -0,0 +1,85 @@ +[ + { + "name": "m.09-01", + + "activeLeg": + { + "vtSymbol": "m1709", + "ratio": 1, + "multiplier": 1.0, + "payup": 2 + }, + + "passiveLegs": [ + { + "vtSymbol": "m1801", + "ratio": -1, + "multiplier": -1.0, + "payup": 2 + } + ] + }, + + { + "name": "IF.07-09", + + "activeLeg": + { + "vtSymbol": "IF1707", + "ratio": 1, + "multiplier": 1.0, + "payup": 2 + }, + + "passiveLegs": [ + { + "vtSymbol": "IF1709", + "ratio": -1, + "multiplier": -1.0, + "payup": 2 + } + ] + }, + + { + "name": "IH.07-09", + + "activeLeg": + { + "vtSymbol": "IH1707", + "ratio": 1, + "multiplier": 1.0, + "payup": 2 + }, + + "passiveLegs": [ + { + "vtSymbol": "IH1709", + "ratio": -1, + "multiplier": -1.0, + "payup": 2 + } + ] + }, + + { + "name": "IC.07-09", + + "activeLeg": + { + "vtSymbol": "IC1707", + "ratio": 1, + "multiplier": 1.0, + "payup": 2 + }, + + "passiveLegs": [ + { + "vtSymbol": "IC1709", + "ratio": -1, + "multiplier": -1.0, + "payup": 2 + } + ] + } +] \ No newline at end of file diff --git a/examples/VnTrader/VT_setting.json b/examples/VnTrader/VT_setting.json new file mode 100644 index 00000000..c9bdcc0b --- /dev/null +++ b/examples/VnTrader/VT_setting.json @@ -0,0 +1,11 @@ +{ + "fontFamily": "微软雅黑", + "fontSize": 12, + + "mongoHost": "localhost", + "mongoPort": 27017, + "mongoLogging": true, + + "darkStyle": true, + "language": "chinese" +} \ No newline at end of file diff --git a/examples/VnTrader/XSPEED_connect.json b/examples/VnTrader/XSPEED_connect.json new file mode 100644 index 00000000..71f0b84a --- /dev/null +++ b/examples/VnTrader/XSPEED_connect.json @@ -0,0 +1,6 @@ +{ + "tdAddress": "tcp://203.187.171.250:10910", + "password": "请联系飞创申请", + "mdAddress": "tcp://203.187.171.250:10915", + "accountID": "请联系飞创申请" +} \ No newline at end of file diff --git a/examples/VnTrader/run.py b/examples/VnTrader/run.py index e174bd53..0af7dabe 100644 --- a/examples/VnTrader/run.py +++ b/examples/VnTrader/run.py @@ -17,8 +17,7 @@ from vnpy.trader.gateway import (ctpGateway, femasGateway, xspeedGateway, shzdGateway, huobiGateway, okcoinGateway) # 加载上层应用 -from vnpy.trader.app import (riskManager, dataRecorder, - ctaStrategy) +from vnpy.trader.app import (riskManager, ctaStrategy, spreadTrading) #---------------------------------------------------------------------- @@ -43,8 +42,8 @@ def main(): # 添加上层应用 me.addApp(riskManager) - me.addApp(dataRecorder) me.addApp(ctaStrategy) + me.addApp(spreadTrading) # 创建主窗口 mw = MainWindow(me, ee) diff --git a/tutorial/tick2trade/vn.ctp_t2t/build.sh b/tutorial/tick2trade/vn.ctp_t2t/build.sh index 83858574..ed8475f8 100644 --- a/tutorial/tick2trade/vn.ctp_t2t/build.sh +++ b/tutorial/tick2trade/vn.ctp_t2t/build.sh @@ -12,6 +12,6 @@ cmake .. make VERBOSE=1 -j 1 ln -fs `pwd`/lib/vnctpmd.so ../vnctpmd/test/vnctpmd.so ln -fs `pwd`/lib/vnctptd.so ../vnctptd/test/vnctptd.so -cp ../vnctpmd/test/vnctpmd.* ../../vn.trader/ctpGateway/ -cp ../vnctptd/test/vnctptd.* ../../vn.trader/ctpGateway/ +cp ../vnctpmd/test/vnctpmd.* ../../../vn.trader/gateway/ctpGateway/ +cp ../vnctptd/test/vnctptd.* ../../../vn.trader/gateway/ctpGateway/ popd diff --git a/vnpy/api/okcoin/vnokcoin.py b/vnpy/api/okcoin/vnokcoin.py index 38565825..75c09c05 100644 --- a/vnpy/api/okcoin/vnokcoin.py +++ b/vnpy/api/okcoin/vnokcoin.py @@ -20,6 +20,7 @@ CURRENCY_USD = 'usd' # 电子货币代码 SYMBOL_BTC = 'btc' SYMBOL_LTC = 'ltc' +SYMBOL_ETH = 'eth' # 行情深度 DEPTH_20 = 20 @@ -42,6 +43,7 @@ INTERVAL_1W = 'week' # 交易代码,需要后缀货币名才能完整 TRADING_SYMBOL_BTC = 'btc_' TRADING_SYMBOL_LTC = 'ltc_' +TRADING_SYMBOL_ETH = 'eth_' # 委托类型 TYPE_BUY = 'buy' diff --git a/vnpy/data/README.md b/vnpy/data/README.md new file mode 100644 index 00000000..b00cb83f --- /dev/null +++ b/vnpy/data/README.md @@ -0,0 +1,5 @@ +# vn.data - 数据相关工具 + +### 历史数据 +* datayes:通联数据接口 +* shcifco:上海中期接口 \ No newline at end of file diff --git a/vnpy/data/__init__.py b/vnpy/data/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/vnpy/data/datayes/__init__.py b/vnpy/data/datayes/__init__.py new file mode 100644 index 00000000..e06b8545 --- /dev/null +++ b/vnpy/data/datayes/__init__.py @@ -0,0 +1 @@ +from vndatayes import DatayesApi \ No newline at end of file diff --git a/vnpy/data/datayes/vndatayes.py b/vnpy/data/datayes/vndatayes.py new file mode 100644 index 00000000..f64aaf68 --- /dev/null +++ b/vnpy/data/datayes/vndatayes.py @@ -0,0 +1,52 @@ +# encoding: UTF-8 + +'''一个简单的通联数据客户端,主要使用requests开发,比通联官网的python例子更为简洁。''' + +import os +import requests +import json + + +HTTP_OK = 200 + + +######################################################################## +class DatayesApi(object): + """通联数据API""" + + #---------------------------------------------------------------------- + def __init__(self, token, + domain="http://api.wmcloud.com/data", + version="v1"): + """Constructor""" + self.domain = domain # 主域名 + self.version = version # API版本 + self.token = token # 授权码 + + self.header = {} # http请求头部 + self.header['Connection'] = 'keep_alive' + self.header['Authorization'] = 'Bearer ' + self.token + + #---------------------------------------------------------------------- + def downloadData(self, path, params): + """下载数据""" + url = '/'.join([self.domain, self.version, path]) + r = requests.get(url=url, headers=self.header, params=params) + + if r.status_code != HTTP_OK: + print u'http请求失败,状态代码%s' %r.status_code + return None + else: + result = r.json() + if 'retMsg' in result and result['retMsg'] == 'Success': + return result['data'] + else: + if 'retMsg' in result: + print u'查询失败,返回信息%s' %result['retMsg'] + elif 'message' in result: + print u'查询失败,返回信息%s' %result['message'] + return None + + + + \ No newline at end of file diff --git a/vnpy/data/shcifco/__init__.py b/vnpy/data/shcifco/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/vnpy/data/shcifco/test.py b/vnpy/data/shcifco/test.py new file mode 100644 index 00000000..eb2f88d8 --- /dev/null +++ b/vnpy/data/shcifco/test.py @@ -0,0 +1,27 @@ +# encoding: UTF-8 + +from vnshcifco import ShcifcoApi, PERIOD_1MIN + + +if __name__ == "__main__": + ip = '180.169.126.123' + port = '45065' + token = '请联系上海中期申请' + symbol = 'cu1709' + + # 创建API对象 + api = ShcifcoApi(ip, port, token) + + # 获取最新tick + print api.getLastTick(symbol) + + # 获取最新价格 + print api.getLastPrice(symbol) + + # 获取最新分钟线 + print api.getLastBar(symbol) + + # 获取历史分钟线 + print api.getHisBar(symbol, 500, period=PERIOD_1MIN) + + \ No newline at end of file diff --git a/vnpy/data/shcifco/vnshcifco.py b/vnpy/data/shcifco/vnshcifco.py new file mode 100644 index 00000000..425d96b8 --- /dev/null +++ b/vnpy/data/shcifco/vnshcifco.py @@ -0,0 +1,147 @@ +# encoding: UTF-8 + + +import requests + +HTTP_OK = 200 + +PERIOD_1MIN = '1m' +PERIOD_5MIN = '5m' +PERIOD_15MIN = '15m' +PERIOD_60MIN = '60m' +PERIOD_1DAY = '1d' + + +######################################################################## +class ShcifcoApi(object): + """数据接口""" + + #---------------------------------------------------------------------- + def __init__(self, ip, port, token): + """Constructor""" + self.ip = ip + self.port = port + self.token = token + + self.service = 'ShcifcoApi' + self.domain = 'http://' + ':'.join([self.ip, self.port]) + + #---------------------------------------------------------------------- + def getData(self, path, params): + """下载数据""" + url = '/'.join([self.domain, self.service, path]) + params['token'] = self.token + r = requests.get(url=url, params=params) + + if r.status_code != HTTP_OK: + print u'http请求失败,状态代码%s' %r.status_code + return None + else: + return r.text + + #---------------------------------------------------------------------- + def getLastTick(self, symbol): + """获取最新Tick""" + path = 'lasttick' + params = {'ids': symbol} + + data = self.getData(path, params) + if not data: + return None + + data = data.split(';')[0] + l = data.split(',') + d = { + 'symbol': l[0], + 'lastPrice': float(l[1]), + 'bidPrice': float(l[2]), + 'bidVolume': int(l[3]), + 'askPrice': float(l[4]), + 'askVolume': int(l[5]), + 'volume': int(l[6]), + 'openInterest': int(l[7]) + } + return d + + #---------------------------------------------------------------------- + def getLastPrice(self, symbol): + """获取最新成交价""" + path = 'lastprice' + params = {'ids': symbol} + + data = self.getData(path, params) + if not data: + return None + + data = data.split(';')[0] + price = float(data) + return price + + #---------------------------------------------------------------------- + def getLastBar(self, symbol): + """获取最新的一分钟K线数据""" + path = 'lastbar' + params = {'id': symbol} + + data = self.getData(path, params) + if not data: + return None + + data = data.split(';')[0] + l = data.split(',') + d = { + 'symbol': l[0], + 'time': l[1], + 'open': float(l[2]), + 'high': float(l[3]), + 'low': float(l[4]), + 'close': float(l[5]), + 'volume': int(l[6]), + 'openInterest': int(l[7]) + } + return d + + #---------------------------------------------------------------------- + def getHisBar(self, symbol, num, date='', period=''): + """获取历史K线数据""" + path = 'hisbar' + + # 默认参数 + params = { + 'id': symbol, + 'num': num + } + # 可选参数 + if date: + params['date'] = date + if period: + params['period'] = period + + data = self.getData(path, params) + if not data: + return None + + barList = [] + l = data.split(';') + + for barStr in l: + # 过滤某些空数据 + if ',' not in barStr: + continue + + barData = barStr.split(',') + d = { + 'symbol': barData[0], + 'date': barData[1], + 'time': barData[2], + 'open': float(barData[3]), + 'high': float(barData[4]), + 'low': float(barData[5]), + 'close': float(barData[6]), + 'volume': int(barData[7]), + 'openInterest': int(barData[8]) + } + barList.append(d) + + return barList + diff --git a/vnpy/rpc/vnrpc.py b/vnpy/rpc/vnrpc.py index c3a979c9..f8c00047 100644 --- a/vnpy/rpc/vnrpc.py +++ b/vnpy/rpc/vnrpc.py @@ -180,7 +180,7 @@ class RpcServer(RpcObject): def publish(self, topic, data): """ 广播推送数据 - topic:主题内容 + topic:主题内容(注意必须是ascii编码) data:具体的数据 """ # 序列化数据 @@ -294,6 +294,8 @@ class RpcClient(RpcObject): 订阅特定主题的广播数据 可以使用topic=''来订阅所有的主题 + + 注意topic必须是ascii编码 """ self.__socketSUB.setsockopt(zmq.SUBSCRIBE, topic) diff --git a/vnpy/trader/app/ctaStrategy/ctaBacktesting.py b/vnpy/trader/app/ctaStrategy/ctaBacktesting.py index 62b07b9d..c3d63f6f 100644 --- a/vnpy/trader/app/ctaStrategy/ctaBacktesting.py +++ b/vnpy/trader/app/ctaStrategy/ctaBacktesting.py @@ -139,7 +139,7 @@ class BacktestingEngine(object): # 载入初始化需要用的数据 flt = {'datetime':{'$gte':self.dataStartDate, '$lt':self.strategyStartDate}} - initCursor = collection.find(flt) + initCursor = collection.find(flt).sort('datetime') # 将数据从查询指针中读取出,并生成列表 self.initData = [] # 清空initData列表 @@ -154,7 +154,7 @@ class BacktestingEngine(object): else: flt = {'datetime':{'$gte':self.strategyStartDate, '$lte':self.dataEndDate}} - self.dbCursor = collection.find(flt) + self.dbCursor = collection.find(flt).sort('datetime') self.output(u'载入完成,数据量:%s' %(initCursor.count() + self.dbCursor.count())) @@ -228,7 +228,6 @@ class BacktestingEngine(object): order.vtSymbol = vtSymbol order.price = self.roundToPriceTick(price) order.totalVolume = volume - order.status = STATUS_NOTTRADED # 刚提交尚未成交 order.orderID = orderID order.vtOrderID = orderID order.orderTime = str(self.dt) @@ -273,8 +272,8 @@ class BacktestingEngine(object): so.price = self.roundToPriceTick(price) so.volume = volume so.strategy = strategy - so.stopOrderID = stopOrderID so.status = STOPORDER_WAITING + so.stopOrderID = stopOrderID if orderType == CTAORDER_BUY: so.direction = DIRECTION_LONG @@ -293,6 +292,9 @@ class BacktestingEngine(object): self.stopOrderDict[stopOrderID] = so self.workingStopOrderDict[stopOrderID] = so + # 推送停止单初始更新 + self.strategy.onStopOrder(so) + return stopOrderID #---------------------------------------------------------------------- @@ -303,6 +305,7 @@ class BacktestingEngine(object): so = self.workingStopOrderDict[stopOrderID] so.status = STOPORDER_CANCELLED del self.workingStopOrderDict[stopOrderID] + self.strategy.onStopOrder(so) #---------------------------------------------------------------------- def crossLimitOrder(self): @@ -321,6 +324,11 @@ class BacktestingEngine(object): # 遍历限价单字典中的所有限价单 for orderID, order in self.workingLimitOrderDict.items(): + # 推送委托进入队列(未成交)的状态更新 + if not order.status: + order.status = STATUS_NOTTRADED + self.strategy.onOrder(order) + # 判断是否会成交 buyCross = (order.direction==DIRECTION_LONG and order.price>=buyCrossPrice and @@ -391,6 +399,11 @@ class BacktestingEngine(object): # 如果发生了成交 if buyCross or sellCross: + # 更新停止单状态,并从字典中删除该停止单 + so.status = STOPORDER_TRIGGERED + if stopOrderID in self.workingStopOrderDict: + del self.workingStopOrderDict[stopOrderID] + # 推送成交数据 self.tradeCount += 1 # 成交编号自增1 tradeID = str(self.tradeCount) @@ -410,19 +423,15 @@ class BacktestingEngine(object): orderID = str(self.limitOrderCount) trade.orderID = orderID trade.vtOrderID = orderID - trade.direction = so.direction trade.offset = so.offset trade.volume = so.volume trade.tradeTime = str(self.dt) trade.dt = self.dt - self.strategy.onTrade(trade) self.tradeDict[tradeID] = trade # 推送委托数据 - so.status = STOPORDER_TRIGGERED - order = VtOrderData() order.vtSymbol = so.vtSymbol order.symbol = so.vtSymbol @@ -435,14 +444,14 @@ class BacktestingEngine(object): order.tradedVolume = so.volume order.status = STATUS_ALLTRADED order.orderTime = trade.tradeTime - self.strategy.onOrder(order) self.limitOrderDict[orderID] = order - # 从字典中删除该限价单 - if stopOrderID in self.workingStopOrderDict: - del self.workingStopOrderDict[stopOrderID] - + # 按照顺序推送数据 + self.strategy.onStopOrder(so) + self.strategy.onOrder(order) + self.strategy.onTrade(trade) + #---------------------------------------------------------------------- def insertData(self, dbName, collectionName, data): """考虑到回测中不允许向数据库插入数据,防止实盘交易中的一些代码出错""" @@ -812,7 +821,7 @@ class BacktestingEngine(object): l.append(pool.apply_async(optimize, (strategyClass, setting, targetName, self.mode, self.startDate, self.initDays, self.endDate, - self.slippage, self.rate, self.size, + self.slippage, self.rate, self.size, self.priceTick, self.dbName, self.symbol))) pool.close() pool.join() @@ -929,7 +938,7 @@ def formatNumber(n): #---------------------------------------------------------------------- def optimize(strategyClass, setting, targetName, mode, startDate, initDays, endDate, - slippage, rate, size, + slippage, rate, size, priceTick, dbName, symbol): """多进程优化时跑在每个进程中运行的函数""" engine = BacktestingEngine() @@ -939,6 +948,7 @@ def optimize(strategyClass, setting, targetName, engine.setSlippage(slippage) engine.setRate(rate) engine.setSize(size) + engine.setPriceTick(priceTick) engine.setDatabase(dbName, symbol) engine.initStrategy(strategyClass, setting) diff --git a/vnpy/trader/app/ctaStrategy/ctaBase.py b/vnpy/trader/app/ctaStrategy/ctaBase.py index 1bc92b79..10e257ad 100644 --- a/vnpy/trader/app/ctaStrategy/ctaBase.py +++ b/vnpy/trader/app/ctaStrategy/ctaBase.py @@ -33,6 +33,10 @@ MINUTE_DB_NAME = 'VnTrader_1Min_Db' ENGINETYPE_BACKTESTING = 'backtesting' # 回测 ENGINETYPE_TRADING = 'trading' # 实盘 +# CTA模块事件 +EVENT_CTA_LOG = 'eCtaLog' # CTA相关的日志事件 +EVENT_CTA_STRATEGY = 'eCtaStrategy.' # CTA策略状态变化事件 + # CTA引擎中涉及的数据类定义 from vnpy.trader.vtConstant import EMPTY_UNICODE, EMPTY_STRING, EMPTY_FLOAT, EMPTY_INT diff --git a/vnpy/trader/app/ctaStrategy/ctaEngine.py b/vnpy/trader/app/ctaStrategy/ctaEngine.py index 2a2178b5..fc6f69d9 100644 --- a/vnpy/trader/app/ctaStrategy/ctaEngine.py +++ b/vnpy/trader/app/ctaStrategy/ctaEngine.py @@ -29,10 +29,10 @@ from vnpy.trader.vtEvent import * from vnpy.trader.vtConstant import * from vnpy.trader.vtObject import VtTickData, VtBarData from vnpy.trader.vtGateway import VtSubscribeReq, VtOrderReq, VtCancelOrderReq, VtLogData -from vnpy.trader.vtFunction import todayDate +from vnpy.trader.vtFunction import todayDate, getJsonPath -from vnpy.trader.app.ctaStrategy.ctaBase import * -from vnpy.trader.app.ctaStrategy.strategy import STRATEGY_CLASS +from .ctaBase import * +from .strategy import STRATEGY_CLASS @@ -41,8 +41,7 @@ from vnpy.trader.app.ctaStrategy.strategy import STRATEGY_CLASS class CtaEngine(object): """CTA策略引擎""" settingFileName = 'CTA_setting.json' - path = os.path.abspath(os.path.dirname(__file__)) - settingFileName = os.path.join(path, settingFileName) + settingfilePath = getJsonPath(settingFileName, __file__) #---------------------------------------------------------------------- def __init__(self, mainEngine, eventEngine): @@ -211,6 +210,9 @@ class CtaEngine(object): self.stopOrderDict[stopOrderID] = so self.workingStopOrderDict[stopOrderID] = so + # 推送停止单状态 + strategy.onStopOrder(so) + return stopOrderID #---------------------------------------------------------------------- @@ -221,6 +223,7 @@ class CtaEngine(object): so = self.workingStopOrderDict[stopOrderID] so.status = STOPORDER_CANCELLED del self.workingStopOrderDict[stopOrderID] + so.strategy.onStopOrder(so) #---------------------------------------------------------------------- def processStopOrder(self, tick): @@ -245,6 +248,7 @@ class CtaEngine(object): so.status = STOPORDER_TRIGGERED self.sendOrder(so.vtSymbol, so.orderType, price, so.volume, so.strategy) del self.workingStopOrderDict[so.stopOrderID] + so.strategy.onStopOrder(so) #---------------------------------------------------------------------- def processTickEvent(self, event): @@ -255,10 +259,15 @@ class CtaEngine(object): # 推送tick到对应的策略实例进行处理 if tick.vtSymbol in self.tickStrategyDict: - # 添加datetime字段 - if not tick.datetime: - tick.datetime = datetime.strptime(' '.join([tick.date, tick.time]), '%Y%m%d %H:%M:%S.%f') - + # tick时间可能出现异常数据,使用try...except实现捕捉和过滤 + try: + # 添加datetime字段 + if not tick.datetime: + tick.datetime = datetime.strptime(' '.join([tick.date, tick.time]), '%Y%m%d %H:%M:%S.%f') + except ValueError: + self.writeCtaLog(traceback.format_exc()) + return + # 逐个推送到策略实例中 l = self.tickStrategyDict[tick.vtSymbol] for strategy in l: @@ -337,7 +346,7 @@ class CtaEngine(object): startDate = self.today - timedelta(days) d = {'datetime':{'$gte':startDate}} - barData = self.mainEngine.dbQuery(dbName, collectionName, d) + barData = self.mainEngine.dbQuery(dbName, collectionName, d, 'datetime') l = [] for d in barData: @@ -352,7 +361,7 @@ class CtaEngine(object): startDate = self.today - timedelta(days) d = {'datetime':{'$gte':startDate}} - tickData = self.mainEngine.dbQuery(dbName, collectionName, d) + tickData = self.mainEngine.dbQuery(dbName, collectionName, d, 'datetime') l = [] for d in tickData: @@ -463,12 +472,30 @@ class CtaEngine(object): if so.strategy is strategy: self.cancelStopOrder(stopOrderID) else: - self.writeCtaLog(u'策略实例不存在:%s' %name) + self.writeCtaLog(u'策略实例不存在:%s' %name) + + #---------------------------------------------------------------------- + def initAll(self): + """全部初始化""" + for name in self.strategyDict.keys(): + self.initStrategy(name) + + #---------------------------------------------------------------------- + def startAll(self): + """全部启动""" + for name in self.strategyDict.keys(): + self.startStrategy(name) + + #---------------------------------------------------------------------- + def stopAll(self): + """全部停止""" + for name in self.strategyDict.keys(): + self.stopStrategy(name) #---------------------------------------------------------------------- def saveSetting(self): """保存策略配置""" - with open(self.settingFileName, 'w') as f: + with open(self.settingfilePath, 'w') as f: l = [] for strategy in self.strategyDict.values(): @@ -483,7 +510,7 @@ class CtaEngine(object): #---------------------------------------------------------------------- def loadSetting(self): """读取策略配置""" - with open(self.settingFileName) as f: + with open(self.settingfilePath) as f: l = json.load(f) for setting in l: diff --git a/vnpy/trader/app/ctaStrategy/ctaHistoryData.py b/vnpy/trader/app/ctaStrategy/ctaHistoryData.py index 5366468d..34b6a284 100644 --- a/vnpy/trader/app/ctaStrategy/ctaHistoryData.py +++ b/vnpy/trader/app/ctaStrategy/ctaHistoryData.py @@ -13,10 +13,11 @@ from multiprocessing.pool import ThreadPool import pymongo +from vnpy.data.datayes import DatayesApi from vnpy.trader.vtGlobal import globalSetting from vnpy.trader.vtConstant import * from vnpy.trader.vtObject import VtBarData -from vnpy.trader.app.ctaStrategy.datayesClient import DatayesClient +from .ctaBase import SETTING_DB_NAME, TICK_DB_NAME, MINUTE_DB_NAME # 以下为vn.trader和通联数据规定的交易所代码映射 @@ -33,10 +34,10 @@ class HistoryDataEngine(object): """CTA模块用的历史数据引擎""" #---------------------------------------------------------------------- - def __init__(self): + def __init__(self, token): """Constructor""" self.dbClient = pymongo.MongoClient(globalSetting['mongoHost'], globalSetting['mongoPort']) - self.datayesClient = DatayesClient() + self.datayesClient = DatayesApi(token) #---------------------------------------------------------------------- def lastTradeDate(self): diff --git a/vnpy/trader/app/ctaStrategy/ctaTemplate.py b/vnpy/trader/app/ctaStrategy/ctaTemplate.py index f9cf9459..b4213d1f 100644 --- a/vnpy/trader/app/ctaStrategy/ctaTemplate.py +++ b/vnpy/trader/app/ctaStrategy/ctaTemplate.py @@ -90,6 +90,11 @@ class CtaTemplate(object): """收到Bar推送(必须由用户继承实现)""" raise NotImplementedError + #---------------------------------------------------------------------- + def onStopOrder(self, so): + """收到停止单推送(必须由用户继承实现)""" + raise NotImplementedError + #---------------------------------------------------------------------- def buy(self, price, volume, stop=False): """买开""" diff --git a/vnpy/trader/app/ctaStrategy/strategy/strategyAtrRsi.py b/vnpy/trader/app/ctaStrategy/strategy/strategyAtrRsi.py index 3669a66b..c9606b96 100644 --- a/vnpy/trader/app/ctaStrategy/strategy/strategyAtrRsi.py +++ b/vnpy/trader/app/ctaStrategy/strategy/strategyAtrRsi.py @@ -237,3 +237,7 @@ class AtrRsiStrategy(CtaTemplate): # 发出状态更新事件 self.putEvent() + #---------------------------------------------------------------------- + def onStopOrder(self, so): + """停止单推送""" + pass \ No newline at end of file diff --git a/vnpy/trader/app/ctaStrategy/strategy/strategyDualThrust.py b/vnpy/trader/app/ctaStrategy/strategy/strategyDualThrust.py index bc5b99b5..c7f7815a 100644 --- a/vnpy/trader/app/ctaStrategy/strategy/strategyDualThrust.py +++ b/vnpy/trader/app/ctaStrategy/strategy/strategyDualThrust.py @@ -221,3 +221,8 @@ class DualThrustStrategy(CtaTemplate): def onTrade(self, trade): # 发出状态更新事件 self.putEvent() + + #---------------------------------------------------------------------- + def onStopOrder(self, so): + """停止单推送""" + pass \ No newline at end of file diff --git a/vnpy/trader/app/ctaStrategy/strategy/strategyEmaDemo.py b/vnpy/trader/app/ctaStrategy/strategy/strategyEmaDemo.py index 5047c444..e1fee112 100644 --- a/vnpy/trader/app/ctaStrategy/strategy/strategyEmaDemo.py +++ b/vnpy/trader/app/ctaStrategy/strategy/strategyEmaDemo.py @@ -188,6 +188,11 @@ class EmaDemoStrategy(CtaTemplate): # 对于无需做细粒度委托控制的策略,可以忽略onOrder pass + #---------------------------------------------------------------------- + def onStopOrder(self, so): + """停止单推送""" + pass + ######################################################################################## class OrderManagementDemoStrategy(CtaTemplate): @@ -294,3 +299,8 @@ class OrderManagementDemoStrategy(CtaTemplate): """收到成交推送(必须由用户继承实现)""" # 对于无需做细粒度委托控制的策略,可以忽略onOrder pass + + #---------------------------------------------------------------------- + def onStopOrder(self, so): + """停止单推送""" + pass diff --git a/vnpy/trader/app/ctaStrategy/strategy/strategyKingKeltner.py b/vnpy/trader/app/ctaStrategy/strategy/strategyKingKeltner.py index 5681a56d..84163ea3 100644 --- a/vnpy/trader/app/ctaStrategy/strategy/strategyKingKeltner.py +++ b/vnpy/trader/app/ctaStrategy/strategy/strategyKingKeltner.py @@ -278,3 +278,8 @@ class KkStrategy(CtaTemplate): # 将委托号记录到列表中 self.orderList.append(self.buyOrderID) self.orderList.append(self.shortOrderID) + + #---------------------------------------------------------------------- + def onStopOrder(self, so): + """停止单推送""" + pass \ No newline at end of file diff --git a/vnpy/trader/app/ctaStrategy/uiCtaWidget.py b/vnpy/trader/app/ctaStrategy/uiCtaWidget.py index f1b077ae..7223a673 100644 --- a/vnpy/trader/app/ctaStrategy/uiCtaWidget.py +++ b/vnpy/trader/app/ctaStrategy/uiCtaWidget.py @@ -9,7 +9,8 @@ from vnpy.event import Event from vnpy.trader.vtEvent import * from vnpy.trader.uiBasicWidget import QtGui, QtCore, QtWidgets, BasicCell -from vnpy.trader.app.ctaStrategy.language import text +from .ctaBase import EVENT_CTA_LOG, EVENT_CTA_STRATEGY +from .language import text ######################################################################## @@ -227,20 +228,17 @@ class CtaEngineManager(QtWidgets.QWidget): #---------------------------------------------------------------------- def initAll(self): """全部初始化""" - for name in self.ctaEngine.strategyDict.keys(): - self.ctaEngine.initStrategy(name) + self.ctaEngine.initAll() #---------------------------------------------------------------------- def startAll(self): """全部启动""" - for name in self.ctaEngine.strategyDict.keys(): - self.ctaEngine.startStrategy(name) + self.ctaEngine.startAll() #---------------------------------------------------------------------- def stopAll(self): """全部停止""" - for name in self.ctaEngine.strategyDict.keys(): - self.ctaEngine.stopStrategy(name) + self.ctaEngine.stopAll() #---------------------------------------------------------------------- def load(self): @@ -267,13 +265,14 @@ class CtaEngineManager(QtWidgets.QWidget): #---------------------------------------------------------------------- def closeEvent(self, event): """关闭窗口时的事件""" - reply = QtWidgets.QMessageBox.question(self, text.SAVE_POSITION_DATA, - text.SAVE_POSITION_QUESTION, QtWidgets.QMessageBox.Yes | - QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No) - - if reply == QtWidgets.QMessageBox.Yes: - self.ctaEngine.savePosition() - + if self.isVisible(): + reply = QtWidgets.QMessageBox.question(self, text.SAVE_POSITION_DATA, + text.SAVE_POSITION_QUESTION, QtWidgets.QMessageBox.Yes | + QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No) + + if reply == QtWidgets.QMessageBox.Yes: + self.ctaEngine.savePosition() + event.accept() diff --git a/vnpy/trader/app/dataRecorder/DR_setting.csv b/vnpy/trader/app/dataRecorder/DR_setting.csv new file mode 100644 index 00000000..fa5f396c --- /dev/null +++ b/vnpy/trader/app/dataRecorder/DR_setting.csv @@ -0,0 +1,7 @@ +gateway,symbol,exchange,currency,product,tick,bar,active +CTP,IF1709,,,,y,y,IF0000 +CTP,IC1709,,,,y,, +CTP,m1709,,,,y,, +SGIT,IH1709,,,,y,y,IH0000 +LTS,600036,SZSE,,,y,, +IB,EUR.USD,IDEALPRO,USD,,y,y, diff --git a/vnpy/trader/app/dataRecorder/DR_setting.json b/vnpy/trader/app/dataRecorder/DR_setting.json index f7c297c5..07030299 100644 --- a/vnpy/trader/app/dataRecorder/DR_setting.json +++ b/vnpy/trader/app/dataRecorder/DR_setting.json @@ -1,32 +1,18 @@ { - "working": false, + "working": true, "tick": [ - ["m1609", "XSPEED"], - ["IF1606", "SGIT"], - ["IH1606", "SGIT"], - ["IH1606", "SGIT"], - ["IC1606", "SGIT"], - ["IC1606", "SGIT"], - ["600036", "LTS", "SZSE"], - ["EUR.USD", "IB", "IDEALPRO", "USD", "外汇"] ], "bar": [ - ["IF1605", "SGIT"], - ["IF1606", "SGIT"], - ["IH1606", "SGIT"], - ["IH1606", "SGIT"], - ["IC1606", "SGIT"], - ["IC1606", "SGIT"] + ["BTC_CNY_SPOT", "OKCOIN"], + ["LTC_CNY_SPOT", "OKCOIN"], + ["ETH_CNY_SPOT", "OKCOIN"] ], "active": { - "IF0000": "IF1605", - "IH0000": "IH1605", - "IC0000": "IC1605" } } \ No newline at end of file diff --git a/vnpy/trader/app/dataRecorder/drBase.py b/vnpy/trader/app/dataRecorder/drBase.py index cddfd90a..43c4f05d 100644 --- a/vnpy/trader/app/dataRecorder/drBase.py +++ b/vnpy/trader/app/dataRecorder/drBase.py @@ -12,6 +12,8 @@ TICK_DB_NAME = 'VnTrader_Tick_Db' DAILY_DB_NAME = 'VnTrader_Daily_Db' MINUTE_DB_NAME = 'VnTrader_1Min_Db' +# 行情记录模块事件 +EVENT_DATARECORDER_LOG = 'eDataRecorderLog' # 行情记录日志更新事件 # CTA引擎中涉及的数据类定义 from vnpy.trader.vtConstant import EMPTY_UNICODE, EMPTY_STRING, EMPTY_FLOAT, EMPTY_INT diff --git a/vnpy/trader/app/dataRecorder/drEngine.py b/vnpy/trader/app/dataRecorder/drEngine.py index 56d8eba2..63a20e57 100644 --- a/vnpy/trader/app/dataRecorder/drEngine.py +++ b/vnpy/trader/app/dataRecorder/drEngine.py @@ -6,7 +6,8 @@ 使用DR_setting.json来配置需要收集的合约,以及主力合约代码。 ''' -import json +#import json +import csv import os import copy from collections import OrderedDict @@ -16,7 +17,7 @@ from threading import Thread from vnpy.event import Event from vnpy.trader.vtEvent import * -from vnpy.trader.vtFunction import todayDate +from vnpy.trader.vtFunction import todayDate, getJsonPath from vnpy.trader.vtObject import VtSubscribeReq, VtLogData, VtBarData, VtTickData from vnpy.trader.app.dataRecorder.drBase import * @@ -28,8 +29,7 @@ class DrEngine(object): """数据记录引擎""" settingFileName = 'DR_setting.json' - path = os.path.abspath(os.path.dirname(__file__)) - settingFileName = os.path.join(path, settingFileName) + settingFilePath = getJsonPath(settingFileName, __file__) #---------------------------------------------------------------------- def __init__(self, mainEngine, eventEngine): @@ -49,6 +49,9 @@ class DrEngine(object): # K线对象字典 self.barDict = {} + # 配置字典 + self.settingDict = OrderedDict() + # 负责执行数据库插入的单独线程相关 self.active = False # 工作状态 self.queue = Queue() # 队列 @@ -57,49 +60,55 @@ class DrEngine(object): # 载入设置,订阅行情 self.loadSetting() + # 启动数据插入线程 + self.start() + + # 注册事件监听 + self.registerEvent() + #---------------------------------------------------------------------- def loadSetting(self): - """载入设置""" + """加载配置""" with open(self.settingFileName) as f: drSetting = json.load(f) - + # 如果working设为False则不启动行情记录功能 working = drSetting['working'] if not working: return - + if 'tick' in drSetting: l = drSetting['tick'] - + for setting in l: symbol = setting[0] vtSymbol = symbol req = VtSubscribeReq() req.symbol = setting[0] - + # 针对LTS和IB接口,订阅行情需要交易所代码 if len(setting)>=3: req.exchange = setting[2] vtSymbol = '.'.join([symbol, req.exchange]) - + # 针对IB接口,订阅行情需要货币和产品类型 if len(setting)>=5: req.currency = setting[3] req.productClass = setting[4] - + self.mainEngine.subscribe(req, setting[1]) - + tick = VtTickData() # 该tick实例可以用于缓存部分数据(目前未使用) self.tickDict[vtSymbol] = tick - + if 'bar' in drSetting: l = drSetting['bar'] - + for setting in l: symbol = setting[0] vtSymbol = symbol - + req = VtSubscribeReq() req.symbol = symbol @@ -110,31 +119,81 @@ class DrEngine(object): if len(setting)>=5: req.currency = setting[3] req.productClass = setting[4] - + self.mainEngine.subscribe(req, setting[1]) - + bar = VtBarData() self.barDict[vtSymbol] = bar - + if 'active' in drSetting: d = drSetting['active'] - + # 注意这里的vtSymbol对于IB和LTS接口,应该后缀.交易所 for activeSymbol, vtSymbol in d.items(): self.activeSymbolDict[vtSymbol] = activeSymbol - + # 启动数据插入线程 self.start() - + # 注册事件监听 - self.registerEvent() + self.registerEvent() + + + ##---------------------------------------------------------------------- + #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 #---------------------------------------------------------------------- def procecssTickEvent(self, event): """处理行情推送""" tick = event.dict_['data'] vtSymbol = tick.vtSymbol - + # 转化Tick格式 if not tick.datetime: tick.datetime = datetime.strptime(' '.join([tick.date, tick.time]), '%Y%m%d %H:%M:%S.%f') @@ -159,7 +218,9 @@ class DrEngine(object): bar = self.barDict[vtSymbol] # 如果第一个TICK或者新的一分钟 - if not bar.datetime or bar.datetime.minute != tick.datetime.minute: + 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) diff --git a/vnpy/trader/app/dataRecorder/uiDrWidget.py b/vnpy/trader/app/dataRecorder/uiDrWidget.py index 49833179..575702b3 100644 --- a/vnpy/trader/app/dataRecorder/uiDrWidget.py +++ b/vnpy/trader/app/dataRecorder/uiDrWidget.py @@ -4,14 +4,10 @@ 行情记录模块相关的GUI控制组件 ''' -import json - -from qtpy import QtWidgets, QtGui, QtCore - from vnpy.event import Event -from vnpy.trader.vtEvent import * - -from vnpy.trader.app.dataRecorder.language import text +from vnpy.trader.uiQt import QtWidgets, QtCore +from .drBase import EVENT_DATARECORDER_LOG +from .language import text ######################################################################## @@ -119,33 +115,24 @@ class DrEngineManager(QtWidgets.QWidget): #---------------------------------------------------------------------- def updateSetting(self): """显示引擎行情记录配置""" - with open(self.drEngine.settingFileName) as f: - drSetting = json.load(f) - - if 'tick' in drSetting: - l = drSetting['tick'] - - for setting in l: - self.tickTable.insertRow(0) - self.tickTable.setItem(0, 0, TableCell(setting[0])) - self.tickTable.setItem(0, 1, TableCell(setting[1])) - - if 'bar' in drSetting: - l = drSetting['bar'] - - for setting in l: - self.barTable.insertRow(0) - self.barTable.setItem(0, 0, TableCell(setting[0])) - self.barTable.setItem(0, 1, TableCell(setting[1])) - - if 'active' in drSetting: - d = drSetting['active'] - - for activeSymbol, symbol in d.items(): - self.activeTable.insertRow(0) - self.activeTable.setItem(0, 0, TableCell(activeSymbol)) - self.activeTable.setItem(0, 1, TableCell(symbol)) - + setting = self.drEngine.getSetting() + + for d in setting.values(): + if d['tick']: + self.tickTable.insertRow(0) + self.tickTable.setItem(0, 0, TableCell(d['symbol'])) + self.tickTable.setItem(0, 1, TableCell(d['gateway'])) + + if d['bar']: + self.barTable.insertRow(0) + self.barTable.setItem(0, 0, TableCell(d['symbol'])) + self.barTable.setItem(0, 1, TableCell(d['gateway'])) + + if d['active']: + self.activeTable.insertRow(0) + self.activeTable.setItem(0, 0, TableCell(d['active'])) + self.activeTable.setItem(0, 1, TableCell(d['symbol'])) + self.tickTable.resizeColumnsToContents() self.barTable.resizeColumnsToContents() self.activeTable.resizeColumnsToContents() diff --git a/vnpy/trader/app/riskManager/RM_setting.json b/vnpy/trader/app/riskManager/RM_setting.json index 1e452dd7..25c98921 100644 --- a/vnpy/trader/app/riskManager/RM_setting.json +++ b/vnpy/trader/app/riskManager/RM_setting.json @@ -2,8 +2,8 @@ "orderFlowClear": 1, "orderCancelLimit": 10, "workingOrderLimit": 20, - "tradeLimit": 100, - "orderSizeLimit": 10, + "tradeLimit": 1000, + "orderSizeLimit": 100, "active": true, "orderFlowLimit": 50 } \ No newline at end of file diff --git a/vnpy/trader/app/riskManager/rmEngine.py b/vnpy/trader/app/riskManager/rmEngine.py index 54ef606e..8816f2b0 100644 --- a/vnpy/trader/app/riskManager/rmEngine.py +++ b/vnpy/trader/app/riskManager/rmEngine.py @@ -15,14 +15,14 @@ from vnpy.event import Event from vnpy.trader.vtEvent import * from vnpy.trader.vtConstant import * from vnpy.trader.vtGateway import VtLogData +from vnpy.trader.vtFunction import getJsonPath ######################################################################## class RmEngine(object): """风控引擎""" settingFileName = 'RM_setting.json' - path = os.path.abspath(os.path.dirname(__file__)) - settingFileName = os.path.join(path, settingFileName) + settingFilePath = getJsonPath(settingFileName, __file__) name = u'风控模块' @@ -64,7 +64,7 @@ class RmEngine(object): #---------------------------------------------------------------------- def loadSetting(self): """读取配置""" - with open(self.settingFileName) as f: + with open(self.settingFilePath) as f: d = json.load(f) # 设置风控参数 @@ -84,7 +84,7 @@ class RmEngine(object): #---------------------------------------------------------------------- def saveSetting(self): """保存风控参数""" - with open(self.settingFileName, 'w') as f: + with open(self.settingFilePath, 'w') as f: # 保存风控参数 d = {} diff --git a/vnpy/trader/app/spreadTrading/ST_setting.json b/vnpy/trader/app/spreadTrading/ST_setting.json new file mode 100644 index 00000000..ba2c1da2 --- /dev/null +++ b/vnpy/trader/app/spreadTrading/ST_setting.json @@ -0,0 +1,85 @@ +[ + { + "name": "m.09-01", + + "activeLeg": + { + "vtSymbol": "m1709", + "ratio": 1, + "multiplier": 1.0, + "payup": 2 + }, + + "passiveLegs": [ + { + "vtSymbol": "m1801", + "ratio": -1, + "multiplier": -1.0, + "payup": 2 + } + ] + }, + + { + "name": "IF.07-09", + + "activeLeg": + { + "vtSymbol": "IF1707", + "ratio": 1, + "multiplier": 1.0, + "payup": 2 + }, + + "passiveLegs": [ + { + "vtSymbol": "IF1709", + "ratio": -1, + "multiplier": -1.0, + "payup": 2 + } + ] + }, + + { + "name": "IH.07-09", + + "activeLeg": + { + "vtSymbol": "IH1707", + "ratio": 1, + "multiplier": 1.0, + "payup": 2 + }, + + "passiveLegs": [ + { + "vtSymbol": "IH1709", + "ratio": -1, + "multiplier": -1.0, + "payup": 2 + } + ] + }, + + { + "name": "IC.07-09", + + "activeLeg": + { + "vtSymbol": "IC1707", + "ratio": 1, + "multiplier": 1.0, + "payup": 2 + }, + + "passiveLegs": [ + { + "vtSymbol": "IC1709", + "ratio": -1, + "multiplier": -1.0, + "payup": 2 + } + ] + } +] \ No newline at end of file diff --git a/vnpy/trader/app/spreadTrading/__init__.py b/vnpy/trader/app/spreadTrading/__init__.py new file mode 100644 index 00000000..722ecaf7 --- /dev/null +++ b/vnpy/trader/app/spreadTrading/__init__.py @@ -0,0 +1,10 @@ +# encoding: UTF-8 + +from .stEngine import StEngine +from .uiStWidget import StManager + +appName = 'SpreadTrading' +appDisplayName = u'价差交易' +appEngine = StEngine +appWidget = StManager +appIco = 'st.ico' \ No newline at end of file diff --git a/vnpy/trader/app/spreadTrading/st.ico b/vnpy/trader/app/spreadTrading/st.ico new file mode 100644 index 00000000..4d0ad199 Binary files /dev/null and b/vnpy/trader/app/spreadTrading/st.ico differ diff --git a/vnpy/trader/app/spreadTrading/stAlgo.py b/vnpy/trader/app/spreadTrading/stAlgo.py new file mode 100644 index 00000000..aa9b4184 --- /dev/null +++ b/vnpy/trader/app/spreadTrading/stAlgo.py @@ -0,0 +1,517 @@ +# encoding: UTF-8 + +from math import floor + +from vnpy.trader.vtConstant import (EMPTY_INT, EMPTY_FLOAT, + EMPTY_STRING, EMPTY_UNICODE, + DIRECTION_LONG, DIRECTION_SHORT, + STATUS_ALLTRADED, STATUS_CANCELLED, STATUS_REJECTED) + + + +######################################################################## +class StAlgoTemplate(object): + """价差算法交易模板""" + MODE_LONGSHORT = u'双向' + MODE_LONGONLY = u'做多' + MODE_SHORTONLY = u'做空' + + SPREAD_LONG = 1 + SPREAD_SHORT = 2 + + #---------------------------------------------------------------------- + def __init__(self, algoEngine, spread): + """Constructor""" + self.algoEngine = algoEngine # 算法引擎 + self.spreadName = spread.name # 价差名称 + self.spread = spread # 价差对象 + + self.algoName = EMPTY_STRING # 算法名称 + + self.active = False # 工作状态 + self.mode = self.MODE_LONGSHORT # 工作模式 + + self.buyPrice = EMPTY_FLOAT # 开平仓价格 + self.sellPrice = EMPTY_FLOAT + self.shortPrice = EMPTY_FLOAT + self.coverPrice = EMPTY_FLOAT + + self.maxPosSize = EMPTY_INT # 最大单边持仓量 + self.maxOrderSize = EMPTY_INT # 最大单笔委托量 + + #---------------------------------------------------------------------- + def updateSpreadTick(self, spread): + """""" + raise NotImplementedError + + #---------------------------------------------------------------------- + def updateSpreadPos(self, spread): + """""" + raise NotImplementedError + + #---------------------------------------------------------------------- + def updateTrade(self, trade): + """""" + raise NotImplementedError + + #---------------------------------------------------------------------- + def updateOrder(self, order): + """""" + raise NotImplementedError + + #---------------------------------------------------------------------- + def updateTimer(self): + """""" + raise NotImplementedError + + #---------------------------------------------------------------------- + def start(self): + """""" + raise NotImplementedError + + #---------------------------------------------------------------------- + def stop(self): + """""" + raise NotImplementedError + + #---------------------------------------------------------------------- + def setBuyPrice(self, buyPrice): + """设置买开的价格""" + self.buyPrice = buyPrice + + #---------------------------------------------------------------------- + def setSellPrice(self, sellPrice): + """设置卖平的价格""" + self.sellPrice = sellPrice + + #---------------------------------------------------------------------- + def setShortPrice(self, shortPrice): + """设置卖开的价格""" + self.shortPrice = shortPrice + + #---------------------------------------------------------------------- + def setCoverPrice(self, coverPrice): + """设置买平的价格""" + self.coverPrice = coverPrice + + #---------------------------------------------------------------------- + def setMode(self, mode): + """设置算法交易方向""" + self.mode = mode + + #---------------------------------------------------------------------- + def setMaxOrderSize(self, maxOrderSize): + """设置最大单笔委托数量""" + self.maxOrderSize = maxOrderSize + + #---------------------------------------------------------------------- + def setMaxPosSize(self, maxPosSize): + """设置最大持仓数量""" + self.maxPosSize = maxPosSize + + #---------------------------------------------------------------------- + def putEvent(self): + """发出算法更新事件""" + self.algoEngine.putAlgoEvent(self) + + #---------------------------------------------------------------------- + def writeLog(self, content): + """输出算法日志""" + prefix = ' '.join([self.spreadName, self.algoName]) + content = ':'.join([prefix, content]) + self.algoEngine.writeLog(content) + + #---------------------------------------------------------------------- + def getAlgoParams(self): + """获取算法参数""" + d = { + "spreadName": self.spreadName, + "algoName": self.algoName, + "buyPrice": self.buyPrice, + "sellPrice": self.sellPrice, + "shortPrice": self.shortPrice, + "coverPrice": self.coverPrice, + "maxOrderSize": self.maxOrderSize, + "maxPosSize": self.maxPosSize, + "mode": self.mode + } + return d + + #---------------------------------------------------------------------- + def setAlgoParams(self, d): + """设置算法参数""" + self.buyPrice = d.get('buyPrice', EMPTY_FLOAT) + self.sellPrice = d.get('sellPrice', EMPTY_FLOAT) + self.shortPrice = d.get('shortPrice', EMPTY_FLOAT) + self.coverPrice = d.get('coverPrice', EMPTY_FLOAT) + self.maxOrderSize = d.get('maxOrderSize', EMPTY_INT) + self.maxPosSize = d.get('maxPosSize', EMPTY_INT) + self.mode = d.get('mode', self.MODE_LONGSHORT) + + +######################################################################## +class SniperAlgo(StAlgoTemplate): + """狙击算法(市价委托)""" + FINISHED_STATUS = [STATUS_ALLTRADED, STATUS_CANCELLED, STATUS_REJECTED] + + #---------------------------------------------------------------------- + def __init__(self, algoEngine, spread): + """Constructor""" + super(SniperAlgo, self).__init__(algoEngine, spread) + + self.algoName = u'Sniper' + self.quoteInterval = 2 # 主动腿报价撤单再发前等待的时间 + self.quoteCount = 0 # 报价计数 + self.hedgeInterval = 2 # 对冲腿对冲撤单再发前的等待时间 + self.hedgeCount = 0 # 对冲计数 + + self.activeVtSymbol = spread.activeLeg.vtSymbol # 主动腿代码 + self.passiveVtSymbols = [leg.vtSymbol for leg in spread.passiveLegs] # 被动腿代码列表 + + # 缓存每条腿对象的字典 + self.legDict = {} + self.legDict[spread.activeLeg.vtSymbol] = spread.activeLeg + for leg in spread.passiveLegs: + self.legDict[leg.vtSymbol] = leg + + self.hedgingTaskDict = {} # 被动腿需要对冲的数量字典 vtSymbol:volume + self.legOrderDict = {} # vtSymbol: list of vtOrderID + self.orderTradedDict = {} # vtOrderID: tradedVolume + + #---------------------------------------------------------------------- + def updateSpreadTick(self, spread): + """价差行情更新""" + self.spread = spread + + # 若算法没有启动则直接返回 + if not self.active: + return + + # 若当前已有主动腿委托则直接返回 + if (self.activeVtSymbol in self.legOrderDict and + self.legOrderDict[self.activeVtSymbol]): + return + + # 允许做多 + if self.mode == self.MODE_LONGSHORT or self.mode == self.MODE_LONGONLY: + # 买入 + if (spread.netPos >= 0 and + spread.netPos < self.maxPosSize and + spread.askPrice <= self.buyPrice): + self.quoteActiveLeg(self.SPREAD_LONG) + self.writeLog(u'买入开仓') + + # 卖出 + elif (spread.netPos > 0 and + spread.bidPrice >= self.sellPrice): + self.quoteActiveLeg(self.SPREAD_SHORT) + self.writeLog(u'卖出平仓') + + # 允许做空 + if self.mode == self.MODE_LONGSHORT or self.mode == self.MODE_SHORTONLY: + # 做空 + if (spread.netPos <= 0 and + spread.netPos > -self.maxPosSize and + spread.bidPrice >= self.shortPrice): + self.quoteActiveLeg(self.SPREAD_SHORT) + self.writeLog(u'卖出开仓') + + # 平空 + elif (spread.netPos < 0 and + spread.askPrice <= self.coverPrice): + self.quoteActiveLeg(self.SPREAD_LONG) + self.writeLog(u'买入平仓') + + #---------------------------------------------------------------------- + def updateSpreadPos(self, spread): + """价差持仓更新""" + self.spread = spread + + #---------------------------------------------------------------------- + def updateTrade(self, trade): + """成交更新""" + pass + + #---------------------------------------------------------------------- + def updateOrder(self, order): + """委托更新""" + if not self.active: + return + + vtOrderID = order.vtOrderID + vtSymbol = order.vtSymbol + newTradedVolume = order.tradedVolume + lastTradedVolume = self.orderTradedDict.get(vtOrderID, 0) + + # 检查是否有新的成交 + if newTradedVolume > lastTradedVolume: + self.orderTradedDict[vtOrderID] = newTradedVolume # 缓存委托已经成交数量 + volume = newTradedVolume - lastTradedVolume # 计算本次成交数量 + + if vtSymbol == self.activeVtSymbol: + self.newActiveLegTrade(vtSymbol, order.direction, volume) + else: + self.newPassiveLegTrade(vtSymbol, order.direction, volume) + + # 处理完成委托 + if order.status in self.FINISHED_STATUS: + vtOrderID = order.vtOrderID + vtSymbol = order.vtSymbol + + # 从委托列表中移除该委托 + orderList = self.legOrderDict[vtSymbol] + + if vtOrderID in orderList: + orderList.remove(vtOrderID) + + # 检查若是被动腿,且已经没有未完成委托,则执行对冲 + if not orderList and vtSymbol in self.passiveVtSymbols: + self.hedgePassiveLeg(vtSymbol) + + #---------------------------------------------------------------------- + def updateTimer(self): + """计时更新""" + if not self.active: + return + + self.quoteCount += 1 + self.hedgeCount += 1 + + # 计时到达报价间隔后,则对尚未成交的主动腿委托全部撤单 + # 收到撤单回报后清空委托列表,等待下次价差更新再发单 + if self.quoteCount > self.quoteInterval: + self.cancelLegOrder(self.activeVtSymbol) + self.quoteCount = 0 + + # 计时到达对冲间隔后,则对尚未成交的全部被动腿委托全部撤单 + # 收到撤单回报后,会自动发送新的对冲委托 + if self.hedgeCount > self.hedgeInterval: + self.cancelAllPassiveLegOrders() + self.hedgeCount = 0 + + #---------------------------------------------------------------------- + def start(self): + """启动""" + # 如果已经运行则直接返回状态 + if self.active: + return self.active + + # 做多检查 + if self.mode != self.MODE_SHORTONLY: + if self.buyPrice >= self.sellPrice: + self.writeLog(u'启动失败,允许多头交易时BuyPrice必须小于SellPrice') + return self.active + + # 做空检查 + if self.mode != self.MODE_LONGONLY: + if self.shortPrice <= self.coverPrice: + self.writeLog(u'启动失败,允许空头交易时ShortPrice必须大于CoverPrice') + return self.active + + # 多空检查 + if self.mode == self.MODE_LONGSHORT: + if self.buyPrice >= self.coverPrice: + self.writeLog(u'启动失败,允许双向交易时BuyPrice必须小于CoverPrice') + return self.active + + if self.shortPrice <= self.sellPrice: + self.writeLog(u'启动失败,允许双向交易时ShortPrice必须大于SellPrice') + return self.active + + # 启动算法 + self.quoteCount = 0 + self.hedgeCount = 0 + + self.active = True + self.writeLog(u'算法启动') + + return self.active + + #---------------------------------------------------------------------- + def stop(self): + """停止""" + if self.active: + self.hedgingTaskDict.clear() + self.cancelAllOrders() + + self.active = False + self.writeLog(u'算法停止') + + return self.active + + #---------------------------------------------------------------------- + def sendLegOrder(self, leg, legVolume): + """发送每条腿的委托""" + vtSymbol = leg.vtSymbol + volume = abs(legVolume) + payup = leg.payup + + # 发送委托 + if legVolume > 0: + price = leg.askPrice + + if leg.shortPos > 0: + orderList = self.algoEngine.cover(vtSymbol, price, volume, payup) + else: + orderList = self.algoEngine.buy(vtSymbol, price, volume, payup) + + elif legVolume < 0: + price = leg.bidPrice + + if leg.longPos > 0: + orderList = self.algoEngine.sell(vtSymbol, price, volume, payup) + else: + orderList = self.algoEngine.short(vtSymbol, price, volume, payup) + + # 保存到字典中 + if vtSymbol not in self.legOrderDict: + self.legOrderDict[vtSymbol] = orderList + else: + self.legOrderDict[vtSymbol].extend(orderList) + + #---------------------------------------------------------------------- + def quoteActiveLeg(self, direction): + """发出主动腿""" + spread = self.spread + + # 首先计算不带正负号的价差委托量 + if direction == self.SPREAD_LONG: + spreadVolume = min(spread.askVolume, + self.maxPosSize - spread.netPos, + self.maxOrderSize) + + # 有价差空头持仓的情况下,则本次委托最多平完空头 + if spread.shortPos > 0: + spreadVolume = min(spreadVolume, spread.shortPos) + else: + spreadVolume = min(spread.bidVolume, + self.maxPosSize + spread.netPos, + self.maxOrderSize) + + # 有价差多头持仓的情况下,则本次委托最多平完多头 + if spread.longPos > 0: + spreadVolume = min(spreadVolume, spread.longPos) + + if spreadVolume <= 0: + return + + # 加上价差方向 + if direction == self.SPREAD_SHORT: + spreadVolume = -spreadVolume + + # 计算主动腿委托量 + leg = self.legDict[self.activeVtSymbol] + legVolume = spreadVolume * leg.ratio + self.sendLegOrder(leg, legVolume) + self.writeLog(u'发出新的主动腿%s狙击单' %self.activeVtSymbol) + + self.quoteCount = 0 # 重置主动腿报价撤单等待计数 + + #---------------------------------------------------------------------- + def hedgePassiveLeg(self, vtSymbol): + """被动腿对冲""" + if vtSymbol not in self.hedgingTaskDict: + return + + orderList = self.legOrderDict.get(vtSymbol, []) + if orderList: + return + + legVolume = self.hedgingTaskDict[vtSymbol] + leg = self.legDict[vtSymbol] + + self.sendLegOrder(leg, legVolume) + self.writeLog(u'发出新的被动腿%s对冲单' %vtSymbol) + + #---------------------------------------------------------------------- + def hedgeAllPassiveLegs(self): + """执行所有被动腿对冲""" + for vtSymbol in self.hedgingTaskDict.keys(): + self.hedgePassiveLeg(vtSymbol) + + self.hedgeCount = 0 # 重置被动腿对冲撤单等待计数 + + #---------------------------------------------------------------------- + def newActiveLegTrade(self, vtSymbol, direction, volume): + """新的主动腿成交""" + # 输出日志 + self.writeLog(u'主动腿%s成交,方向%s,数量%s' %(vtSymbol, direction, volume)) + + # 将主动腿成交带上方向 + if direction == DIRECTION_SHORT: + volume = -volume + + # 计算主动腿成交后,对应的价差仓位 + spread = self.spread + activeRatio = spread.activeLeg.ratio + spreadVolume = round(volume / activeRatio) # 四舍五入求主动腿成交量对应的价差份数 + + # 计算价差新仓位,对应的被动腿需要对冲部分 + for leg in self.spread.passiveLegs: + newHedgingTask = leg.ratio * spreadVolume + + if leg.vtSymbol not in self.hedgingTaskDict: + self.hedgingTaskDict[leg.vtSymbol] = newHedgingTask + else: + self.hedgingTaskDict[leg.vtSymbol] += newHedgingTask + + # 发出被动腿对冲委托 + self.hedgeAllPassiveLegs() + + #---------------------------------------------------------------------- + def newPassiveLegTrade(self, vtSymbol, direction, volume): + """新的被动腿成交""" + if vtSymbol in self.hedgingTaskDict: + # 计算完成的对冲数量 + if direction == DIRECTION_LONG: + hedgedVolume = volume + else: + hedgedVolume = -volume + + # 计算剩余尚未完成的数量 + self.hedgingTaskDict[vtSymbol] -= hedgedVolume + + # 如果已全部完成,则从字典中移除 + if not self.hedgingTaskDict[vtSymbol]: + del self.hedgingTaskDict[vtSymbol] + + # 输出日志 + self.writeLog(u'被动腿%s成交,方向%s,数量%s' %(vtSymbol, direction, volume)) + + #---------------------------------------------------------------------- + def cancelLegOrder(self, vtSymbol): + """撤销某条腿的委托""" + if vtSymbol not in self.legOrderDict: + return + + orderList = self.legOrderDict[vtSymbol] + if not orderList: + return + + for vtOrderID in orderList: + self.algoEngine.cancelOrder(vtOrderID) + + self.writeLog(u'撤单%s的所有委托' %vtSymbol) + + #---------------------------------------------------------------------- + def cancelAllOrders(self): + """撤销全部委托""" + for orderList in self.legOrderDict.values(): + for vtOrderID in orderList: + self.algoEngine.cancelOrder(vtOrderID) + + self.writeLog(u'全部撤单') + + #---------------------------------------------------------------------- + def cancelAllPassiveLegOrders(self): + """撤销全部被动腿委托""" + cancelPassive = False + + for vtSymbol in self.passiveVtSymbols: + if vtSymbol in self.legOrderDict and self.legOrderDict[vtSymbol]: + self.cancelLegOrder(vtSymbol) + cancelPassive = True + + # 只有确实发出撤单委托时,才输出信息 + if cancelPassive: + self.writeLog(u'被动腿全撤') diff --git a/vnpy/trader/app/spreadTrading/stBase.py b/vnpy/trader/app/spreadTrading/stBase.py new file mode 100644 index 00000000..bead63f4 --- /dev/null +++ b/vnpy/trader/app/spreadTrading/stBase.py @@ -0,0 +1,168 @@ +# encoding: UTF-8 + +from __future__ import division + +from math import floor +from datetime import datetime + +from vnpy.trader.vtConstant import (EMPTY_INT, EMPTY_FLOAT, + EMPTY_STRING, EMPTY_UNICODE) + + + +EVENT_SPREADTRADING_TICK = 'eSpreadTradingTick.' +EVENT_SPREADTRADING_POS = 'eSpreadTradingPos.' +EVENT_SPREADTRADING_LOG = 'eSpreadTradingLog' +EVENT_SPREADTRADING_ALGO = 'eSpreadTradingAlgo.' +EVENT_SPREADTRADING_ALGOLOG = 'eSpreadTradingAlgoLog' + + + +######################################################################## +class StLeg(object): + """""" + + #---------------------------------------------------------------------- + def __init__(self): + """Constructor""" + self.vtSymbol = EMPTY_STRING # 代码 + self.ratio = EMPTY_INT # 实际交易时的比例 + self.multiplier = EMPTY_FLOAT # 计算价差时的乘数 + self.payup = EMPTY_INT # 对冲时的超价tick + + self.bidPrice = EMPTY_FLOAT + self.askPrice = EMPTY_FLOAT + self.bidVolume = EMPTY_INT + self.askVolume = EMPTY_INT + + self.longPos = EMPTY_INT + self.shortPos = EMPTY_INT + self.netPos = EMPTY_INT + + +######################################################################## +class StSpread(object): + """""" + + #---------------------------------------------------------------------- + def __init__(self): + """Constructor""" + self.name = EMPTY_UNICODE # 名称 + self.symbol = EMPTY_STRING # 代码(基于组成腿计算) + + self.activeLeg = None # 主动腿 + self.passiveLegs = [] # 被动腿(支持多条) + self.allLegs = [] # 所有腿 + + self.bidPrice = EMPTY_FLOAT + self.askPrice = EMPTY_FLOAT + self.bidVolume = EMPTY_INT + self.askVolume = EMPTY_INT + self.time = EMPTY_STRING + + self.longPos = EMPTY_INT + self.shortPos = EMPTY_INT + self.netPos = EMPTY_INT + + #---------------------------------------------------------------------- + def initSpread(self): + """初始化价差""" + # 价差最少要有一条主动腿 + if not self.activeLeg: + return + + # 生成所有腿列表 + self.allLegs.append(self.activeLeg) + self.allLegs.extend(self.passiveLegs) + + # 生成价差代码 + legSymbolList = [] + + for leg in self.allLegs: + if leg.multiplier >= 0: + legSymbol = '+%s*%s' %(leg.multiplier, leg.vtSymbol) + else: + legSymbol = '%s*%s' %(leg.multiplier, leg.vtSymbol) + legSymbolList.append(legSymbol) + + self.symbol = ''.join(legSymbolList) + + #---------------------------------------------------------------------- + def calculatePrice(self): + """计算价格""" + # 清空价格和委托量数据 + self.bidPrice = EMPTY_FLOAT + self.askPrice = EMPTY_FLOAT + self.askVolume = EMPTY_INT + self.bidVolume = EMPTY_INT + + # 遍历价差腿列表 + for n, leg in enumerate(self.allLegs): + # 计算价格 + if leg.multiplier > 0: + self.bidPrice += leg.bidPrice * leg.multiplier + self.askPrice += leg.askPrice * leg.multiplier + else: + self.bidPrice += leg.askPrice * leg.multiplier + self.askPrice += leg.bidPrice * leg.multiplier + + # 计算报单量 + if leg.ratio > 0: + legAdjustedBidVolume = floor(leg.bidVolume / leg.ratio) + legAdjustedAskVolume = floor(leg.askVolume / leg.ratio) + else: + legAdjustedBidVolume = floor(leg.askVolume / abs(leg.ratio)) + legAdjustedAskVolume = floor(leg.bidVolume / abs(leg.ratio)) + + if n == 0: + self.bidVolume = legAdjustedBidVolume # 对于第一条腿,直接初始化 + self.askVolume = legAdjustedAskVolume + else: + self.bidVolume = min(self.bidVolume, legAdjustedBidVolume) # 对于后续的腿,价差可交易报单量取较小值 + self.askVolume = min(self.askVolume, legAdjustedAskVolume) + + # 更新时间 + self.time = datetime.now().strftime('%H:%M:%S.%f')[:-3] + + #---------------------------------------------------------------------- + def calculatePos(self): + """计算持仓""" + # 清空持仓数据 + self.longPos = EMPTY_INT + self.shortPos = EMPTY_INT + self.netPos = EMPTY_INT + + # 遍历价差腿列表 + for n, leg in enumerate(self.allLegs): + if leg.ratio > 0: + legAdjustedLongPos = floor(leg.longPos / leg.ratio) + legAdjustedShortPos = floor(leg.shortPos / leg.ratio) + else: + legAdjustedLongPos = floor(leg.shortPos / abs(leg.ratio)) + legAdjustedShortPos = floor(leg.longPos / abs(leg.ratio)) + + if n == 0: + self.longPos = legAdjustedLongPos + self.shortPos = legAdjustedShortPos + else: + self.longPos = min(self.longPos, legAdjustedLongPos) + self.shortPos = min(self.shortPos, legAdjustedShortPos) + + # 计算净仓位 + self.longPos = int(self.longPos) + self.shortPos = int(self.shortPos) + self.netPos = self.longPos - self.shortPos + + #---------------------------------------------------------------------- + def addActiveLeg(self, leg): + """添加主动腿""" + self.activeLeg = leg + + #---------------------------------------------------------------------- + def addPassiveLeg(self, leg): + """添加被动腿""" + self.passiveLegs.append(leg) + + + + \ No newline at end of file diff --git a/vnpy/trader/app/spreadTrading/stEngine.py b/vnpy/trader/app/spreadTrading/stEngine.py new file mode 100644 index 00000000..808bf565 --- /dev/null +++ b/vnpy/trader/app/spreadTrading/stEngine.py @@ -0,0 +1,584 @@ +# encoding: UTF-8 + +import json +import traceback +import shelve + +from vnpy.event import Event +from vnpy.trader.vtFunction import getJsonPath, getTempPath +from vnpy.trader.vtEvent import (EVENT_TICK, EVENT_TRADE, EVENT_POSITION, + EVENT_TIMER, EVENT_ORDER) +from vnpy.trader.vtObject import (VtSubscribeReq, VtOrderReq, + VtCancelOrderReq, VtLogData) +from vnpy.trader.vtConstant import (DIRECTION_LONG, DIRECTION_SHORT, + OFFSET_OPEN, OFFSET_CLOSE, + PRICETYPE_LIMITPRICE) + +from .stBase import (StLeg, StSpread, EVENT_SPREADTRADING_TICK, + EVENT_SPREADTRADING_POS, EVENT_SPREADTRADING_LOG, + EVENT_SPREADTRADING_ALGO, EVENT_SPREADTRADING_ALGOLOG) +from .stAlgo import SniperAlgo + + +######################################################################## +class StDataEngine(object): + """价差数据计算引擎""" + settingFileName = 'ST_setting.json' + settingFilePath = getJsonPath(settingFileName, __file__) + + #---------------------------------------------------------------------- + def __init__(self, mainEngine, eventEngine): + """Constructor""" + self.mainEngine = mainEngine + self.eventEngine = eventEngine + + # 腿、价差相关字典 + self.legDict = {} # vtSymbol:StLeg + self.spreadDict = {} # name:StSpread + self.vtSymbolSpreadDict = {} # vtSymbol:StSpread + + self.registerEvent() + + #---------------------------------------------------------------------- + def loadSetting(self): + """加载配置""" + try: + with open(self.settingFilePath) as f: + l = json.load(f) + + for setting in l: + result, msg = self.createSpread(setting) + self.writeLog(msg) + + self.writeLog(u'价差配置加载完成') + except: + content = u'价差配置加载出错,原因:' + traceback.format_exc() + self.writeLog(content) + + #---------------------------------------------------------------------- + def saveSetting(self): + """保存配置""" + with open(self.settingFilePath) as f: + pass + + #---------------------------------------------------------------------- + def createSpread(self, setting): + """创建价差""" + result = False + msg = '' + + # 检查价差重名 + if setting['name'] in self.spreadDict: + msg = u'%s价差存在重名' %setting['name'] + return result, msg + + # 检查腿是否已使用 + l = [] + l.append(setting['activeLeg']['vtSymbol']) + for d in setting['passiveLegs']: + l.append(d['vtSymbol']) + + for vtSymbol in l: + if vtSymbol in self.vtSymbolSpreadDict: + existingSpread = self.vtSymbolSpreadDict[vtSymbol] + msg = u'%s合约已经存在于%s价差中' %(vtSymbol, existingSpread.name) + return result, msg + + # 创建价差 + spread = StSpread() + spread.name = setting['name'] + self.spreadDict[spread.name] = spread + + # 创建主动腿 + activeSetting = setting['activeLeg'] + + activeLeg = StLeg() + activeLeg.vtSymbol = str(activeSetting['vtSymbol']) + activeLeg.ratio = float(activeSetting['ratio']) + activeLeg.multiplier = float(activeSetting['multiplier']) + activeLeg.payup = int(activeSetting['payup']) + + spread.addActiveLeg(activeLeg) + self.legDict[activeLeg.vtSymbol] = activeLeg + self.vtSymbolSpreadDict[activeLeg.vtSymbol] = spread + + self.subscribeMarketData(activeLeg.vtSymbol) + + # 创建被动腿 + passiveSettingList = setting['passiveLegs'] + passiveLegList = [] + + for d in passiveSettingList: + passiveLeg = StLeg() + passiveLeg.vtSymbol = str(d['vtSymbol']) + passiveLeg.ratio = float(d['ratio']) + passiveLeg.multiplier = float(d['multiplier']) + passiveLeg.payup = int(d['payup']) + + spread.addPassiveLeg(passiveLeg) + self.legDict[passiveLeg.vtSymbol] = passiveLeg + self.vtSymbolSpreadDict[passiveLeg.vtSymbol] = spread + + self.subscribeMarketData(passiveLeg.vtSymbol) + + # 初始化价差 + spread.initSpread() + + self.putSpreadTickEvent(spread) + self.putSpreadPosEvent(spread) + + # 返回结果 + result = True + msg = u'%s价差创建成功' %spread.name + return result, msg + + #---------------------------------------------------------------------- + def processTickEvent(self, event): + """处理行情推送""" + # 检查行情是否需要处理 + tick = event.dict_['data'] + if tick.vtSymbol not in self.legDict: + return + + # 更新腿价格 + leg = self.legDict[tick.vtSymbol] + leg.bidPrice = tick.bidPrice1 + leg.askPrice = tick.askPrice1 + leg.bidVolume = tick.bidVolume1 + leg.askVolume = tick.askVolume1 + + # 更新价差价格 + spread = self.vtSymbolSpreadDict[tick.vtSymbol] + spread.calculatePrice() + + # 发出事件 + self.putSpreadTickEvent(spread) + + #---------------------------------------------------------------------- + def putSpreadTickEvent(self, spread): + """发出价差行情更新事件""" + event1 = Event(EVENT_SPREADTRADING_TICK+spread.name) + event1.dict_['data'] = spread + self.eventEngine.put(event1) + + event2 = Event(EVENT_SPREADTRADING_TICK) + event2.dict_['data'] = spread + self.eventEngine.put(event2) + + #---------------------------------------------------------------------- + def processTradeEvent(self, event): + """处理成交推送""" + # 检查成交是否需要处理 + trade = event.dict_['data'] + if trade.vtSymbol not in self.legDict: + return + + # 更新腿持仓 + leg = self.legDict[trade.vtSymbol] + direction = trade.direction + offset = trade.offset + + if direction == DIRECTION_LONG: + if offset == OFFSET_OPEN: + leg.longPos += trade.volume + else: + leg.shortPos -= trade.volume + else: + if offset == OFFSET_OPEN: + leg.shortPos += trade.volume + else: + leg.longPos -= trade.volume + leg.netPos = leg.longPos - leg.shortPos + + # 更新价差持仓 + spread = self.vtSymbolSpreadDict[trade.vtSymbol] + spread.calculatePos() + + # 推送价差持仓更新 + event1 = Event(EVENT_SPREADTRADING_POS+spread.name) + event1.dict_['data'] = spread + self.eventEngine.put(event1) + + event2 = Event(EVENT_SPREADTRADING_POS) + event2.dict_['data'] = spread + self.eventEngine.put(event2) + + #---------------------------------------------------------------------- + def processPosEvent(self, event): + """处理持仓推送""" + # 检查持仓是否需要处理 + pos = event.dict_['data'] + if pos.vtSymbol not in self.legDict: + return + + # 更新腿持仓 + leg = self.legDict[pos.vtSymbol] + direction = pos.direction + + if direction == DIRECTION_LONG: + leg.longPos = pos.position + else: + leg.shortPos = pos.position + leg.netPos = leg.longPos - leg.shortPos + + # 更新价差持仓 + spread = self.vtSymbolSpreadDict[pos.vtSymbol] + spread.calculatePos() + + # 推送价差持仓更新 + self.putSpreadPosEvent(spread) + + #---------------------------------------------------------------------- + def putSpreadPosEvent(self, spread): + """发出价差持仓事件""" + event1 = Event(EVENT_SPREADTRADING_POS+spread.name) + event1.dict_['data'] = spread + self.eventEngine.put(event1) + + event2 = Event(EVENT_SPREADTRADING_POS) + event2.dict_['data'] = spread + self.eventEngine.put(event2) + + + #---------------------------------------------------------------------- + def registerEvent(self): + """""" + self.eventEngine.register(EVENT_TICK, self.processTickEvent) + self.eventEngine.register(EVENT_TRADE, self.processTradeEvent) + self.eventEngine.register(EVENT_POSITION, self.processPosEvent) + + #---------------------------------------------------------------------- + def subscribeMarketData(self, vtSymbol): + """订阅行情""" + contract = self.mainEngine.getContract(vtSymbol) + if not contract: + self.writeLog(u'订阅行情失败,找不到该合约%s' %vtSymbol) + return + + req = VtSubscribeReq() + req.symbol = contract.symbol + req.exchange = contract.exchange + + self.mainEngine.subscribe(req, contract.gatewayName) + + #---------------------------------------------------------------------- + def writeLog(self, content): + """发出日志""" + log = VtLogData() + log.logContent = content + + event = Event(EVENT_SPREADTRADING_LOG) + event.dict_['data'] = log + self.eventEngine.put(event) + + #---------------------------------------------------------------------- + def getAllSpreads(self): + """获取所有的价差""" + return self.spreadDict.values() + + +######################################################################## +class StAlgoEngine(object): + """价差算法交易引擎""" + algoFileName = 'SpreadTradingAlgo.vt' + algoFilePath = getTempPath(algoFileName) + + #---------------------------------------------------------------------- + def __init__(self, dataEngine, mainEngine, eventEngine): + """Constructor""" + self.dataEngine = dataEngine + self.mainEngine = mainEngine + self.eventEngine = eventEngine + + self.algoDict = {} # spreadName:algo + self.vtSymbolAlgoDict = {} # vtSymbol:algo + + self.registerEvent() + + #---------------------------------------------------------------------- + def registerEvent(self): + """注册事件监听""" + self.eventEngine.register(EVENT_SPREADTRADING_TICK, self.processSpreadTickEvent) + self.eventEngine.register(EVENT_SPREADTRADING_POS, self.processSpreadPosEvent) + self.eventEngine.register(EVENT_TRADE, self.processTradeEvent) + self.eventEngine.register(EVENT_ORDER, self.processOrderEvent) + self.eventEngine.register(EVENT_TIMER, self.processTimerEvent) + + #---------------------------------------------------------------------- + def processSpreadTickEvent(self, event): + """处理价差行情事件""" + spread = event.dict_['data'] + + algo = self.algoDict.get(spread.name, None) + if algo: + algo.updateSpreadTick(spread) + + #---------------------------------------------------------------------- + def processSpreadPosEvent(self, event): + """处理价差持仓事件""" + spread = event.dict_['data'] + + algo = self.algoDict.get(spread.name, None) + if algo: + algo.updateSpreadPos(spread) + + #---------------------------------------------------------------------- + def processTradeEvent(self, event): + """处理成交事件""" + trade = event.dict_['data'] + + algo = self.vtSymbolAlgoDict.get(trade.vtSymbol, None) + if algo: + algo.updateTrade(trade) + + #---------------------------------------------------------------------- + def processOrderEvent(self, event): + """处理委托事件""" + order = event.dict_['data'] + algo = self.vtSymbolAlgoDict.get(order.vtSymbol, None) + + if algo: + algo.updateOrder(order) + + #---------------------------------------------------------------------- + def processTimerEvent(self, event): + """""" + for algo in self.algoDict.values(): + algo.updateTimer() + + #---------------------------------------------------------------------- + def sendOrder(self, vtSymbol, direction, offset, price, volume, payup=0): + """发单""" + contract = self.mainEngine.getContract(vtSymbol) + if not contract: + return '' + + req = VtOrderReq() + req.symbol = contract.symbol + req.exchange = contract.exchange + req.direction = direction + req.offset = offset + req.volume = int(volume) + req.priceType = PRICETYPE_LIMITPRICE + + if direction == DIRECTION_LONG: + req.price = price + payup * contract.priceTick + else: + req.price = price - payup * contract.priceTick + + vtOrderID = self.mainEngine.sendOrder(req, contract.gatewayName) + return vtOrderID + + #---------------------------------------------------------------------- + def cancelOrder(self, vtOrderID): + """撤单""" + order = self.mainEngine.getOrder(vtOrderID) + if not order: + return + + req = VtCancelOrderReq() + req.symbol = order.symbol + req.exchange = order.exchange + req.frontID = order.frontID + req.sessionID = order.sessionID + req.orderID = order.orderID + + self.mainEngine.cancelOrder(req, order.gatewayName) + + #---------------------------------------------------------------------- + def buy(self, vtSymbol, price, volume, payup=0): + """买入""" + vtOrderID = self.sendOrder(vtSymbol, DIRECTION_LONG, OFFSET_OPEN, price, volume, payup) + l = [] + + if vtOrderID: + l.append(vtOrderID) + + return l + + #---------------------------------------------------------------------- + def sell(self, vtSymbol, price, volume, payup=0): + """卖出""" + vtOrderID = self.sendOrder(vtSymbol, DIRECTION_SHORT, OFFSET_CLOSE, price, volume, payup) + l = [] + + if vtOrderID: + l.append(vtOrderID) + + return l + + #---------------------------------------------------------------------- + def short(self, vtSymbol, price, volume, payup=0): + """卖空""" + vtOrderID = self.sendOrder(vtSymbol, DIRECTION_SHORT, OFFSET_OPEN, price, volume, payup) + l = [] + + if vtOrderID: + l.append(vtOrderID) + + return l + + #---------------------------------------------------------------------- + def cover(self, vtSymbol, price, volume, payup=0): + """平空""" + vtOrderID = self.sendOrder(vtSymbol, DIRECTION_LONG, OFFSET_CLOSE, price, volume, payup) + l = [] + + if vtOrderID: + l.append(vtOrderID) + + return l + + #---------------------------------------------------------------------- + def putAlgoEvent(self, algo): + """发出算法状态更新事件""" + event = Event(EVENT_SPREADTRADING_ALGO+algo.name) + self.eventEngine.put(event) + + #---------------------------------------------------------------------- + def writeLog(self, content): + """输出日志""" + log = VtLogData() + log.logContent = content + + event = Event(EVENT_SPREADTRADING_ALGOLOG) + event.dict_['data'] = log + + self.eventEngine.put(event) + + #---------------------------------------------------------------------- + def saveSetting(self): + """保存算法配置""" + setting = {} + for algo in self.algoDict.values(): + setting[algo.spreadName] = algo.getAlgoParams() + + f = shelve.open(self.algoFilePath) + f['setting'] = setting + f.close() + + #---------------------------------------------------------------------- + def loadSetting(self): + """加载算法配置""" + # 创建算法对象 + l = self.dataEngine.getAllSpreads() + for spread in l: + algo = SniperAlgo(self, spread) + self.algoDict[spread.name] = algo + + # 保存腿代码和算法对象的映射 + for leg in spread.allLegs: + self.vtSymbolAlgoDict[leg.vtSymbol] = algo + + # 加载配置 + f = shelve.open(self.algoFilePath) + setting = f.get('setting', None) + f.close() + + if not setting: + return + + for algo in self.algoDict.values(): + if algo.spreadName in setting: + d = setting[algo.spreadName] + algo.setAlgoParams(d) + + #---------------------------------------------------------------------- + def stopAll(self): + """停止全部算法""" + for algo in self.algoDict.values(): + algo.stop() + + #---------------------------------------------------------------------- + def startAlgo(self, spreadName): + """启动算法""" + algo = self.algoDict[spreadName] + algoActive = algo.start() + return algoActive + + #---------------------------------------------------------------------- + def stopAlgo(self, spreadName): + """停止算法""" + algo = self.algoDict[spreadName] + algoActive = algo.stop() + return algoActive + + #---------------------------------------------------------------------- + def getAllAlgoParams(self): + """获取所有算法的参数""" + return [algo.getAlgoParams() for algo in self.algoDict.values()] + + #---------------------------------------------------------------------- + def setAlgoBuyPrice(self, spreadName, buyPrice): + """设置算法买开价格""" + algo = self.algoDict[spreadName] + algo.setBuyPrice(buyPrice) + + #---------------------------------------------------------------------- + def setAlgoSellPrice(self, spreadName, sellPrice): + """设置算法卖平价格""" + algo = self.algoDict[spreadName] + algo.setSellPrice(sellPrice) + + #---------------------------------------------------------------------- + def setAlgoShortPrice(self, spreadName, shortPrice): + """设置算法卖开价格""" + algo = self.algoDict[spreadName] + algo.setShortPrice(shortPrice) + + #---------------------------------------------------------------------- + def setAlgoCoverPrice(self, spreadName, coverPrice): + """设置算法买平价格""" + algo = self.algoDict[spreadName] + algo.setCoverPrice(coverPrice) + + #---------------------------------------------------------------------- + def setAlgoMode(self, spreadName, mode): + """设置算法工作模式""" + algo = self.algoDict[spreadName] + algo.setMode(mode) + + #---------------------------------------------------------------------- + def setAlgoMaxOrderSize(self, spreadName, maxOrderSize): + """设置算法单笔委托限制""" + algo = self.algoDict[spreadName] + algo.setMaxOrderSize(maxOrderSize) + + #---------------------------------------------------------------------- + def setAlgoMaxPosSize(self, spreadName, maxPosSize): + """设置算法持仓限制""" + algo = self.algoDict[spreadName] + algo.setMaxPosSize(maxPosSize) + + +######################################################################## +class StEngine(object): + """价差引擎""" + + #---------------------------------------------------------------------- + def __init__(self, mainEngine, eventEngine): + """Constructor""" + self.mainEngine = mainEngine + self.eventEngine = eventEngine + + self.dataEngine = StDataEngine(mainEngine, eventEngine) + self.algoEngine = StAlgoEngine(self.dataEngine, mainEngine, eventEngine) + + #---------------------------------------------------------------------- + def init(self): + """初始化""" + self.dataEngine.loadSetting() + self.algoEngine.loadSetting() + + #---------------------------------------------------------------------- + def stop(self): + """停止""" + self.dataEngine.saveSetting() + + self.algoEngine.stopAll() + self.algoEngine.saveSetting() + + + + + \ No newline at end of file diff --git a/vnpy/trader/app/spreadTrading/uiStWidget.py b/vnpy/trader/app/spreadTrading/uiStWidget.py new file mode 100644 index 00000000..178b4e55 --- /dev/null +++ b/vnpy/trader/app/spreadTrading/uiStWidget.py @@ -0,0 +1,521 @@ +# encoding: UTF-8 + +from collections import OrderedDict + +from vnpy.event import Event +from vnpy.trader.uiQt import QtWidgets, QtCore +from vnpy.trader.uiBasicWidget import (BasicMonitor, BasicCell, PnlCell, + AskCell, BidCell, BASIC_FONT) + +from .stBase import (EVENT_SPREADTRADING_TICK, EVENT_SPREADTRADING_POS, + EVENT_SPREADTRADING_LOG, EVENT_SPREADTRADING_ALGO, + EVENT_SPREADTRADING_ALGOLOG) +from .stAlgo import StAlgoTemplate + + +STYLESHEET_START = 'background-color: rgb(111,255,244); color: black' +STYLESHEET_STOP = 'background-color: rgb(255,201,111); color: black' + + +######################################################################## +class StTickMonitor(BasicMonitor): + """价差行情监控""" + + #---------------------------------------------------------------------- + def __init__(self, mainEngine, eventEngine, parent=None): + """Constructor""" + super(StTickMonitor, self).__init__(mainEngine, eventEngine, parent) + + d = OrderedDict() + d['name'] = {'chinese':u'价差名称', 'cellType':BasicCell} + d['bidPrice'] = {'chinese':u'买价', 'cellType':BidCell} + d['bidVolume'] = {'chinese':u'买量', 'cellType':BidCell} + d['askPrice'] = {'chinese':u'卖价', 'cellType':AskCell} + d['askVolume'] = {'chinese':u'卖量', 'cellType':AskCell} + d['time'] = {'chinese':u'时间', 'cellType':BasicCell} + d['symbol'] = {'chinese':u'价差公式', 'cellType':BasicCell} + self.setHeaderDict(d) + + self.setDataKey('name') + self.setEventType(EVENT_SPREADTRADING_TICK) + self.setFont(BASIC_FONT) + + self.initTable() + self.registerEvent() + + +######################################################################## +class StPosMonitor(BasicMonitor): + """价差持仓监控""" + + #---------------------------------------------------------------------- + def __init__(self, mainEngine, eventEngine, parent=None): + """Constructor""" + super(StPosMonitor, self).__init__(mainEngine, eventEngine, parent) + + d = OrderedDict() + d['name'] = {'chinese':u'价差名称', 'cellType':BasicCell} + d['netPos'] = {'chinese':u'净仓', 'cellType':PnlCell} + d['longPos'] = {'chinese':u'多仓', 'cellType':BasicCell} + d['shortPos'] = {'chinese':u'空仓', 'cellType':BasicCell} + d['symbol'] = {'chinese':u'代码', 'cellType':BasicCell} + self.setHeaderDict(d) + + self.setDataKey('name') + self.setEventType(EVENT_SPREADTRADING_POS) + self.setFont(BASIC_FONT) + + self.initTable() + self.registerEvent() + + +######################################################################## +class StLogMonitor(QtWidgets.QTextEdit): + """价差日志监控""" + signal = QtCore.pyqtSignal(type(Event())) + + #---------------------------------------------------------------------- + def __init__(self, mainEngine, eventEngine, parent=None): + """Constructor""" + super(StLogMonitor, self).__init__(parent) + + self.eventEngine = eventEngine + + self.registerEvent() + + #---------------------------------------------------------------------- + def processLogEvent(self, event): + """处理日志事件""" + log = event.dict_['data'] + content = '%s:%s' %(log.logTime, log.logContent) + self.append(content) + + #---------------------------------------------------------------------- + def registerEvent(self): + """注册事件监听""" + self.signal.connect(self.processLogEvent) + + self.eventEngine.register(EVENT_SPREADTRADING_LOG, self.signal.emit) + + +######################################################################## +class StAlgoLogMonitor(BasicMonitor): + """价差日志监控""" + + #---------------------------------------------------------------------- + def __init__(self, mainEngine, eventEngine, parent=None): + """Constructor""" + super(StAlgoLogMonitor, self).__init__(mainEngine, eventEngine, parent) + + d = OrderedDict() + d['logTime'] = {'chinese':u'时间', 'cellType':BasicCell} + d['logContent'] = {'chinese':u'信息', 'cellType':BasicCell} + self.setHeaderDict(d) + + self.setEventType(EVENT_SPREADTRADING_ALGOLOG) + self.setFont(BASIC_FONT) + + self.initTable() + self.registerEvent() + + +######################################################################## +class StBuyPriceSpinBox(QtWidgets.QDoubleSpinBox): + """""" + + #---------------------------------------------------------------------- + def __init__(self, algoEngine, spreadName, price, parent=None): + """Constructor""" + super(StBuyPriceSpinBox, self).__init__(parent) + + self.algoEngine = algoEngine + self.spreadName = spreadName + + self.setDecimals(4) + self.setRange(-10000, 10000) + self.setValue(price) + + self.valueChanged.connect(self.setPrice) + + #---------------------------------------------------------------------- + def setPrice(self, value): + """设置价格""" + self.algoEngine.setAlgoBuyPrice(self.spreadName, value) + + +######################################################################## +class StSellPriceSpinBox(QtWidgets.QDoubleSpinBox): + """""" + + #---------------------------------------------------------------------- + def __init__(self, algoEngine, spreadName, price, parent=None): + """Constructor""" + super(StSellPriceSpinBox, self).__init__(parent) + + self.algoEngine = algoEngine + self.spreadName = spreadName + + self.setDecimals(4) + self.setRange(-10000, 10000) + self.setValue(price) + + self.valueChanged.connect(self.setPrice) + + #---------------------------------------------------------------------- + def setPrice(self, value): + """设置价格""" + self.algoEngine.setAlgoSellPrice(self.spreadName, value) + + +######################################################################## +class StShortPriceSpinBox(QtWidgets.QDoubleSpinBox): + """""" + + #---------------------------------------------------------------------- + def __init__(self, algoEngine, spreadName, price, parent=None): + """Constructor""" + super(StShortPriceSpinBox, self).__init__(parent) + + self.algoEngine = algoEngine + self.spreadName = spreadName + + self.setDecimals(4) + self.setRange(-10000, 10000) + self.setValue(price) + + self.valueChanged.connect(self.setPrice) + + #---------------------------------------------------------------------- + def setPrice(self, value): + """设置价格""" + self.algoEngine.setAlgoShortPrice(self.spreadName, value) + + +######################################################################## +class StCoverPriceSpinBox(QtWidgets.QDoubleSpinBox): + """""" + + #---------------------------------------------------------------------- + def __init__(self, algoEngine, spreadName, price, parent=None): + """Constructor""" + super(StCoverPriceSpinBox, self).__init__(parent) + + self.algoEngine = algoEngine + self.spreadName = spreadName + + self.setDecimals(4) + self.setRange(-10000, 10000) + self.setValue(price) + + self.valueChanged.connect(self.setPrice) + + #---------------------------------------------------------------------- + def setPrice(self, value): + """设置价格""" + self.algoEngine.setAlgoCoverPrice(self.spreadName, value) + + +######################################################################## +class StMaxPosSizeSpinBox(QtWidgets.QSpinBox): + """""" + + #---------------------------------------------------------------------- + def __init__(self, algoEngine, spreadName, size, parent=None): + """Constructor""" + super(StMaxPosSizeSpinBox, self).__init__(parent) + + self.algoEngine = algoEngine + self.spreadName = spreadName + + self.setRange(-10000, 10000) + self.setValue(size) + + self.valueChanged.connect(self.setSize) + + #---------------------------------------------------------------------- + def setSize(self, size): + """设置价格""" + self.algoEngine.setAlgoMaxPosSize(self.spreadName, size) + + +######################################################################## +class StMaxOrderSizeSpinBox(QtWidgets.QSpinBox): + """""" + + #---------------------------------------------------------------------- + def __init__(self, algoEngine, spreadName, size, parent=None): + """Constructor""" + super(StMaxOrderSizeSpinBox, self).__init__(parent) + + self.algoEngine = algoEngine + self.spreadName = spreadName + + self.setRange(-10000, 10000) + self.setValue(size) + + self.valueChanged.connect(self.setSize) + + #---------------------------------------------------------------------- + def setSize(self, size): + """设置价格""" + self.algoEngine.setAlgoMaxOrderSize(self.spreadName, size) + + +######################################################################## +class StModeComboBox(QtWidgets.QComboBox): + """""" + + #---------------------------------------------------------------------- + def __init__(self, algoEngine, spreadName, mode, parent=None): + """Constructor""" + super(StModeComboBox, self).__init__(parent) + + self.algoEngine = algoEngine + self.spreadName = spreadName + + l = [StAlgoTemplate.MODE_LONGSHORT, + StAlgoTemplate.MODE_LONGONLY, + StAlgoTemplate.MODE_SHORTONLY] + self.addItems(l) + self.setCurrentIndex(l.index(mode)) + + self.currentIndexChanged.connect(self.setMode) + + #---------------------------------------------------------------------- + def setMode(self): + """设置模式""" + mode = unicode(self.currentText()) + self.algoEngine.setAlgoMode(self.spreadName, mode) + + #---------------------------------------------------------------------- + def algoActiveChanged(self, active): + """算法运行状态改变""" + # 只允许算法停止时修改运行模式 + if active: + self.setEnabled(False) + else: + self.setEnabled(True) + + +######################################################################## +class StActiveButton(QtWidgets.QPushButton): + """""" + signalActive = QtCore.pyqtSignal(bool) + + #---------------------------------------------------------------------- + def __init__(self, algoEngine, spreadName, parent=None): + """Constructor""" + super(StActiveButton, self).__init__(parent) + + self.algoEngine = algoEngine + self.spreadName = spreadName + + self.active = False + self.setStopped() + + self.clicked.connect(self.buttonClicked) + + #---------------------------------------------------------------------- + def buttonClicked(self): + """改变运行模式""" + if self.active: + self.stop() + else: + self.start() + + #---------------------------------------------------------------------- + def stop(self): + """停止""" + algoActive = self.algoEngine.stopAlgo(self.spreadName) + if not algoActive: + self.setStopped() + + #---------------------------------------------------------------------- + def start(self): + """启动""" + algoActive = self.algoEngine.startAlgo(self.spreadName) + if algoActive: + self.setStarted() + + #---------------------------------------------------------------------- + def setStarted(self): + """算法启动""" + self.setText(u'运行中') + self.setStyleSheet(STYLESHEET_START) + + self.active = True + self.signalActive.emit(self.active) + + #---------------------------------------------------------------------- + def setStopped(self): + """算法停止""" + self.setText(u'已停止') + self.setStyleSheet(STYLESHEET_STOP) + + self.active = False + self.signalActive.emit(self.active) + + +######################################################################## +class StAlgoManager(QtWidgets.QTableWidget): + """价差算法管理组件""" + + #---------------------------------------------------------------------- + def __init__(self, stEngine, parent=None): + """Constructor""" + super(StAlgoManager, self).__init__(parent) + + self.algoEngine = stEngine.algoEngine + + self.buttonActiveDict = {} # spreadName: buttonActive + + self.initUi() + + #---------------------------------------------------------------------- + def initUi(self): + """初始化表格""" + headers = [u'价差', + u'算法', + 'BuyPrice', + 'SellPrice', + 'CoverPrice', + 'ShortPrice', + u'委托上限', + u'持仓上限', + u'模式', + u'状态'] + self.setColumnCount(len(headers)) + self.setHorizontalHeaderLabels(headers) + self.horizontalHeader().setResizeMode(QtWidgets.QHeaderView.Stretch) + + self.verticalHeader().setVisible(False) + self.setEditTriggers(self.NoEditTriggers) + + #---------------------------------------------------------------------- + def initCells(self): + """初始化单元格""" + algoEngine = self.algoEngine + + l = self.algoEngine.getAllAlgoParams() + self.setRowCount(len(l)) + + for row, d in enumerate(l): + cellSpreadName = QtWidgets.QTableWidgetItem(d['spreadName']) + cellAlgoName = QtWidgets.QTableWidgetItem(d['algoName']) + spinBuyPrice = StBuyPriceSpinBox(algoEngine, d['spreadName'], d['buyPrice']) + spinSellPrice = StSellPriceSpinBox(algoEngine, d['spreadName'], d['sellPrice']) + spinShortPrice = StShortPriceSpinBox(algoEngine, d['spreadName'], d['shortPrice']) + spinCoverPrice = StCoverPriceSpinBox(algoEngine, d['spreadName'], d['coverPrice']) + spinMaxOrderSize = StMaxOrderSizeSpinBox(algoEngine, d['spreadName'], d['maxOrderSize']) + spinMaxPosSize = StMaxPosSizeSpinBox(algoEngine, d['spreadName'], d['maxPosSize']) + comboMode = StModeComboBox(algoEngine, d['spreadName'], d['mode']) + buttonActive = StActiveButton(algoEngine, d['spreadName']) + + self.setItem(row, 0, cellSpreadName) + self.setItem(row, 1, cellAlgoName) + self.setCellWidget(row, 2, spinBuyPrice) + self.setCellWidget(row, 3, spinSellPrice) + self.setCellWidget(row, 4, spinCoverPrice) + self.setCellWidget(row, 5, spinShortPrice) + self.setCellWidget(row, 6, spinMaxOrderSize) + self.setCellWidget(row, 7, spinMaxPosSize) + self.setCellWidget(row, 8, comboMode) + self.setCellWidget(row, 9, buttonActive) + + buttonActive.signalActive.connect(comboMode.algoActiveChanged) + + self.buttonActiveDict[d['spreadName']] = buttonActive + + #---------------------------------------------------------------------- + def stopAll(self): + """停止所有算法""" + for button in self.buttonActiveDict.values(): + button.stop() + + +######################################################################## +class StGroup(QtWidgets.QGroupBox): + """集合显示""" + + #---------------------------------------------------------------------- + def __init__(self, widget, title, parent=None): + """Constructor""" + super(StGroup, self).__init__(parent) + + self.setTitle(title) + vbox = QtWidgets.QVBoxLayout() + vbox.addWidget(widget) + self.setLayout(vbox) + + +######################################################################## +class StManager(QtWidgets.QWidget): + """""" + + #---------------------------------------------------------------------- + def __init__(self, stEngine, eventEngine, parent=None): + """Constructor""" + super(StManager, self).__init__(parent) + + self.stEngine = stEngine + self.mainEngine = stEngine.mainEngine + self.eventEngine = eventEngine + + self.initUi() + + #---------------------------------------------------------------------- + def initUi(self): + """初始化界面""" + self.setWindowTitle(u'价差交易') + + # 创建组件 + tickMonitor = StTickMonitor(self.mainEngine, self.eventEngine) + posMonitor = StPosMonitor(self.mainEngine, self.eventEngine) + logMonitor = StLogMonitor(self.mainEngine, self.eventEngine) + self.algoManager = StAlgoManager(self.stEngine) + algoLogMonitor = StAlgoLogMonitor(self.mainEngine, self.eventEngine) + + # 创建按钮 + buttonInit = QtWidgets.QPushButton(u'初始化') + buttonInit.clicked.connect(self.init) + + buttonStopAll = QtWidgets.QPushButton(u'全部停止') + buttonStopAll.clicked.connect(self.algoManager.stopAll) + + # 创建集合 + groupTick = StGroup(tickMonitor, u'价差行情') + groupPos = StGroup(posMonitor, u'价差持仓') + groupLog = StGroup(logMonitor, u'日志信息') + groupAlgo = StGroup(self.algoManager, u'价差算法') + groupAlgoLog = StGroup(algoLogMonitor, u'算法信息') + + # 设置布局 + hbox = QtWidgets.QHBoxLayout() + hbox.addWidget(buttonInit) + hbox.addStretch() + hbox.addWidget(buttonStopAll) + + grid = QtWidgets.QGridLayout() + grid.addLayout(hbox, 0, 0, 1, 2) + grid.addWidget(groupTick, 1, 0) + grid.addWidget(groupPos, 1, 1) + grid.addWidget(groupAlgo, 2, 0, 1, 2) + grid.addWidget(groupLog, 3, 0) + grid.addWidget(groupAlgoLog, 3, 1) + + self.setLayout(grid) + + #---------------------------------------------------------------------- + def show(self): + """重载显示""" + self.showMaximized() + + #---------------------------------------------------------------------- + def init(self): + """初始化""" + self.stEngine.init() + self.algoManager.initCells() + + + + \ No newline at end of file diff --git a/vnpy/trader/gateway/cshshlpGateway/cshshlpGateway.py b/vnpy/trader/gateway/cshshlpGateway/cshshlpGateway.py index 49a7968e..8e8689cb 100644 --- a/vnpy/trader/gateway/cshshlpGateway/cshshlpGateway.py +++ b/vnpy/trader/gateway/cshshlpGateway/cshshlpGateway.py @@ -8,7 +8,7 @@ from time import sleep from vnpy.api.cshshlp import CsHsHlp from vnpy.api.ctp import MdApi from vnpy.trader.vtGateway import * -from vnpy.trader.vtFunction import getTempPath +from vnpy.trader.vtFunction import getTempPath, getJsonPath # 接口常量 @@ -92,16 +92,15 @@ class CshshlpGateway(VtGateway): self.qryEnabled = False # 是否要启动循环查询 + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) + #---------------------------------------------------------------------- def connect(self): """连接""" # 载入json文件 - fileName = self.gatewayName + '_connect.json' - path = os.path.abspath(os.path.dirname(__file__)) - fileName = os.path.join(path, fileName) - try: - f = file(fileName) + f = file(self.filePath) except IOError: log = VtLogData() log.gatewayName = self.gatewayName diff --git a/vnpy/trader/gateway/ctpGateway/ctpGateway.py b/vnpy/trader/gateway/ctpGateway/ctpGateway.py index 619eacea..f9390b2a 100644 --- a/vnpy/trader/gateway/ctpGateway/ctpGateway.py +++ b/vnpy/trader/gateway/ctpGateway/ctpGateway.py @@ -15,9 +15,9 @@ from datetime import datetime from vnpy.api.ctp import MdApi, TdApi, defineDict from vnpy.trader.vtGateway import * -from vnpy.trader.vtFunction import getTempPath -from vnpy.trader.gateway.ctpGateway.language import text +from vnpy.trader.vtFunction import getJsonPath, getTempPath from vnpy.trader.vtConstant import GATEWAYTYPE_FUTURES +from .language import text # 以下为一些VT类型和CTP类型的映射字典 @@ -91,19 +91,15 @@ class CtpGateway(VtGateway): self.tdConnected = False # 交易API连接状态 self.qryEnabled = False # 循环查询 - - self.requireAuthentication = False + + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) #---------------------------------------------------------------------- def connect(self): """连接""" - # 载入json文件 - fileName = self.gatewayName + '_connect.json' - path = os.path.abspath(os.path.dirname(__file__)) - fileName = os.path.join(path, fileName) - try: - f = file(fileName) + f = file(self.filePath) except IOError: log = VtLogData() log.gatewayName = self.gatewayName @@ -331,10 +327,6 @@ class CtpMdApi(MdApi): #---------------------------------------------------------------------- def onRtnDepthMarketData(self, data): """行情推送""" - # 忽略成交量为0的无效tick数据 - if not data['Volume']: - return - # 创建对象 tick = VtTickData() tick.gatewayName = self.gatewayName @@ -712,7 +704,7 @@ class CtpTdApi(TdApi): pos.positionProfit += data['PositionProfit'] # 计算持仓均价 - if pos.position: + if pos.position and pos.symbol in self.symbolSizeDict: size = self.symbolSizeDict[pos.symbol] pos.price = (cost + data['PositionCost']) / (pos.position * size) diff --git a/vnpy/trader/gateway/femasGateway/femasGateway.py b/vnpy/trader/gateway/femasGateway/femasGateway.py index 35051d3f..c5962a49 100644 --- a/vnpy/trader/gateway/femasGateway/femasGateway.py +++ b/vnpy/trader/gateway/femasGateway/femasGateway.py @@ -11,7 +11,7 @@ import os import json from vnpy.api.femas import MdApi, TdApi, defineDict -from vnpy.trader.vtFunction import getTempPath +from vnpy.trader.vtFunction import getTempPath, getJsonPath from vnpy.trader.vtGateway import * # 以下为一些VT类型和CTP类型的映射字典 @@ -72,16 +72,15 @@ class FemasGateway(VtGateway): self.qryEnabled = False # 是否要启动循环查询 + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) + #---------------------------------------------------------------------- def connect(self): """连接""" # 载入json文件 - fileName = self.gatewayName + '_connect.json' - path = os.path.abspath(os.path.dirname(__file__)) - fileName = os.path.join(path, fileName) - try: - f = file(fileName) + f = file(self.filePath) except IOError: log = VtLogData() log.gatewayName = self.gatewayName diff --git a/vnpy/trader/gateway/huobiGateway/huobiGateway.py b/vnpy/trader/gateway/huobiGateway/huobiGateway.py index d84fee10..cb36f3c2 100644 --- a/vnpy/trader/gateway/huobiGateway/huobiGateway.py +++ b/vnpy/trader/gateway/huobiGateway/huobiGateway.py @@ -15,7 +15,7 @@ from threading import Thread from vnpy.api.huobi import vnhuobi from vnpy.trader.vtGateway import * - +from vnpy.trader.vtFunction import getJsonPath SYMBOL_BTCCNY = 'BTCCNY' SYMBOL_LTCCNY = 'LTCCNY' @@ -60,16 +60,15 @@ class HuobiGateway(VtGateway): self.tradeApi = HuobiTradeApi(self) self.dataApi = HuobiDataApi(self) + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) + #---------------------------------------------------------------------- def connect(self): """连接""" # 载入json文件 - fileName = self.gatewayName + '_connect.json' - path = os.path.abspath(os.path.dirname(__file__)) - fileName = os.path.join(path, fileName) - try: - f = file(fileName) + f = file(self.filePath) except IOError: log = VtLogData() log.gatewayName = self.gatewayName diff --git a/vnpy/trader/gateway/ibGateway/ibGateway.py b/vnpy/trader/gateway/ibGateway/ibGateway.py index 880768b0..6104f002 100644 --- a/vnpy/trader/gateway/ibGateway/ibGateway.py +++ b/vnpy/trader/gateway/ibGateway/ibGateway.py @@ -19,7 +19,9 @@ from copy import copy from vnpy.api.ib import * from vnpy.trader.vtGateway import * -from vnpy.trader.gateway.ibGateway.language import text +from vnpy.trader.vtFunction import getJsonPath +from .language import text + # 以下为一些VT类型和CTP类型的映射字典 @@ -136,17 +138,16 @@ class IbGateway(VtGateway): self.connected = False # 连接状态 self.api = IbWrapper(self) # API接口 + + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) #---------------------------------------------------------------------- def connect(self): """连接""" # 载入json文件 - fileName = self.gatewayName + '_connect.json' - path = os.path.abspath(os.path.dirname(__file__)) - fileName = os.path.join(path, fileName) - try: - f = file(fileName) + f = file(self.filePath) except IOError: log = VtLogData() log.gatewayName = self.gatewayName diff --git a/vnpy/trader/gateway/ksgoldGateway/ksgoldGateway.py b/vnpy/trader/gateway/ksgoldGateway/ksgoldGateway.py index 80f22f91..d7ebb69e 100644 --- a/vnpy/trader/gateway/ksgoldGateway/ksgoldGateway.py +++ b/vnpy/trader/gateway/ksgoldGateway/ksgoldGateway.py @@ -13,6 +13,7 @@ import time from vnpy.api.ksgold import TdApi, defineDict from vnpy.trader.vtGateway import * +from vnpy.trader.vtFunction import getJsonPath # 以下类型映射参考的是原生API里的Constant.h @@ -45,16 +46,15 @@ class KsgoldGateway(VtGateway): self.orderInited = False # 委托初始化查询 self.tradeInited = False # 成交初始化查询 + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) + #---------------------------------------------------------------------- def connect(self): """连接""" # 载入json文件 - fileName = self.gatewayName + '_connect.json' - path = os.path.abspath(os.path.dirname(__file__)) - fileName = os.path.join(path, fileName) - try: - f = file(fileName) + f = file(self.filePath) except IOError: log = VtLogData() log.gatewayName = self.gatewayName diff --git a/vnpy/trader/gateway/ksotpGateway/ksotpGateway.py b/vnpy/trader/gateway/ksotpGateway/ksotpGateway.py index f6dd286b..c5b0a465 100644 --- a/vnpy/trader/gateway/ksotpGateway/ksotpGateway.py +++ b/vnpy/trader/gateway/ksotpGateway/ksotpGateway.py @@ -9,7 +9,7 @@ import os import json from vnpy.api.ksotp import MdApi, TdApi, defineDict -from vnpy.trader.vtFunction import getTempPath +from vnpy.trader.vtFunction import getTempPath, getJsonPath from vnpy.trader.vtGateway import * # 以下为一些VT类型和CTP类型的映射字典 @@ -68,16 +68,15 @@ class KsotpGateway(VtGateway): self.qryEnabled = False # 是否要启动循环查询 + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) + #---------------------------------------------------------------------- def connect(self): """连接""" # 载入json文件 - fileName = self.gatewayName + '_connect.json' - path = os.path.abspath(os.path.dirname(__file__)) - fileName = os.path.join(path, fileName) - try: - f = file(fileName) + f = file(self.filePath) except IOError: log = VtLogData() log.gatewayName = self.gatewayName diff --git a/vnpy/trader/gateway/lhangGateway/lhangGateway.py b/vnpy/trader/gateway/lhangGateway/lhangGateway.py index d8d250b8..174906e6 100644 --- a/vnpy/trader/gateway/lhangGateway/lhangGateway.py +++ b/vnpy/trader/gateway/lhangGateway/lhangGateway.py @@ -12,6 +12,7 @@ from time import sleep from vnpy.api.lhang import LhangApi from vnpy.trader.vtGateway import * +from vnpy.trader.vtFunction import getJsonPath SYMBOL_BTCCNY = 'BTCCNY' @@ -46,16 +47,15 @@ class LhangGateway(VtGateway): self.api = LhangApi(self) + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) + #---------------------------------------------------------------------- def connect(self): """连接""" # 载入json文件 - fileName = self.gatewayName + '_connect.json' - path = os.path.abspath(os.path.dirname(__file__)) - fileName = os.path.join(path, fileName) - try: - f = file(fileName) + f = file(self.filePath) except IOError: log = VtLogData() log.gatewayName = self.gatewayName diff --git a/vnpy/trader/gateway/ltsGateway/ltsGateway.py b/vnpy/trader/gateway/ltsGateway/ltsGateway.py index 5616c5c5..83610ac2 100644 --- a/vnpy/trader/gateway/ltsGateway/ltsGateway.py +++ b/vnpy/trader/gateway/ltsGateway/ltsGateway.py @@ -8,7 +8,7 @@ import os import json from vnpy.api.lts import MdApi, QryApi, TdApi, defineDict -from vnpy.trader.vtFunction import getTempPath +from vnpy.trader.vtFunction import getTempPath, getJsonPath from vnpy.trader.vtGateway import * @@ -68,17 +68,16 @@ class LtsGateway(VtGateway): self.qryConnected = False self.qryEnabled = False # 是否要启动循环查询 + + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) #---------------------------------------------------------------------- def connect(self): """连接""" # 载入json 文件 - fileName = self.gatewayName + '_connect.json' - path = os.path.abspath(os.path.dirname(__file__)) - fileName = os.path.join(path, fileName) - try: - f = file(fileName) + f = file(self.filePath) except IOError: log = VtLogData() log.gatewayName = self.gatewayName diff --git a/vnpy/trader/gateway/oandaGateway/oandaGateway.py b/vnpy/trader/gateway/oandaGateway/oandaGateway.py index f4e52bac..642c81bf 100644 --- a/vnpy/trader/gateway/oandaGateway/oandaGateway.py +++ b/vnpy/trader/gateway/oandaGateway/oandaGateway.py @@ -26,6 +26,7 @@ import datetime from vnpy.api.oanda import OandaApi from vnpy.trader.vtGateway import * +from vnpy.trader.vtFunction import getJsonPath # 价格类型映射 priceTypeMap = {} @@ -53,16 +54,15 @@ class OandaGateway(VtGateway): self.qryEnabled = False # 是否要启动循环查询 + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) + #---------------------------------------------------------------------- def connect(self): """连接""" # 载入json文件 - fileName = self.gatewayName + '_connect.json' - path = os.path.abspath(os.path.dirname(__file__)) - fileName = os.path.join(path, fileName) - try: - f = file(fileName) + f = file(self.filePath) except IOError: log = VtLogData() log.gatewayName = self.gatewayName diff --git a/vnpy/trader/gateway/okcoinGateway/okcoinGateway.py b/vnpy/trader/gateway/okcoinGateway/okcoinGateway.py index 2791c49f..0fd23e7d 100644 --- a/vnpy/trader/gateway/okcoinGateway/okcoinGateway.py +++ b/vnpy/trader/gateway/okcoinGateway/okcoinGateway.py @@ -11,13 +11,16 @@ vn.okcoin的gateway接入 import os import json from datetime import datetime +from time import sleep from copy import copy from threading import Condition from Queue import Queue from threading import Thread +from time import sleep from vnpy.api.okcoin import vnokcoin from vnpy.trader.vtGateway import * +from vnpy.trader.vtFunction import getJsonPath # 价格类型映射 priceTypeMap = {} @@ -54,16 +57,24 @@ LTC_USD_THISWEEK = 'LTC_USD_THISWEEK' LTC_USD_NEXTWEEK = 'LTC_USD_NEXTWEEK' LTC_USD_QUARTER = 'LTC_USD_QUARTER' +ETH_USD_SPOT = 'ETH_USD_SPOT' +ETH_USD_THISWEEK = 'ETH_USD_THISWEEK' +ETH_USD_NEXTWEEK = 'ETH_USD_NEXTWEEK' +ETH_USD_QUARTER = 'ETH_USD_QUARTER' + # CNY BTC_CNY_SPOT = 'BTC_CNY_SPOT' LTC_CNY_SPOT = 'LTC_CNY_SPOT' +ETH_CNY_SPOT = 'ETH_CNY_SPOT' # 印射字典 spotSymbolMap = {} spotSymbolMap['ltc_usd'] = LTC_USD_SPOT spotSymbolMap['btc_usd'] = BTC_USD_SPOT +spotSymbolMap['ETH_usd'] = ETH_USD_SPOT spotSymbolMap['ltc_cny'] = LTC_CNY_SPOT spotSymbolMap['btc_cny'] = BTC_CNY_SPOT +spotSymbolMap['eth_cny'] = ETH_CNY_SPOT spotSymbolMapReverse = {v: k for k, v in spotSymbolMap.items()} @@ -75,16 +86,20 @@ channelSymbolMap = {} # USD channelSymbolMap['ok_sub_spotusd_btc_ticker'] = BTC_USD_SPOT channelSymbolMap['ok_sub_spotusd_ltc_ticker'] = LTC_USD_SPOT +channelSymbolMap['ok_sub_spotusd_eth_ticker'] = ETH_USD_SPOT channelSymbolMap['ok_sub_spotusd_btc_depth_20'] = BTC_USD_SPOT channelSymbolMap['ok_sub_spotusd_ltc_depth_20'] = LTC_USD_SPOT +channelSymbolMap['ok_sub_spotusd_eth_depth_20'] = ETH_USD_SPOT # CNY channelSymbolMap['ok_sub_spotcny_btc_ticker'] = BTC_CNY_SPOT channelSymbolMap['ok_sub_spotcny_ltc_ticker'] = LTC_CNY_SPOT +channelSymbolMap['ok_sub_spotcny_eth_ticker'] = ETH_CNY_SPOT channelSymbolMap['ok_sub_spotcny_btc_depth_20'] = BTC_CNY_SPOT channelSymbolMap['ok_sub_spotcny_ltc_depth_20'] = LTC_CNY_SPOT +channelSymbolMap['ok_sub_spotcny_eth_depth_20'] = ETH_CNY_SPOT @@ -103,16 +118,15 @@ class OkcoinGateway(VtGateway): self.leverage = 0 self.connected = False + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) + #---------------------------------------------------------------------- def connect(self): """连接""" # 载入json文件 - fileName = self.gatewayName + '_connect.json' - path = os.path.abspath(os.path.dirname(__file__)) - fileName = os.path.join(path, fileName) - try: - f = file(fileName) + f = file(self.filePath) except IOError: log = VtLogData() log.gatewayName = self.gatewayName @@ -305,17 +319,20 @@ class Api(vnokcoin.OkCoinApi): self.spotOrderInfo(vnokcoin.TRADING_SYMBOL_LTC, '-1') self.spotOrderInfo(vnokcoin.TRADING_SYMBOL_BTC, '-1') - + self.spotOrderInfo(vnokcoin.TRADING_SYMBOL_ETH, '-1') + # 连接后订阅现货的成交和账户数据 self.subscribeSpotTrades() self.subscribeSpotUserInfo() self.subscribeSpotTicker(vnokcoin.SYMBOL_BTC) self.subscribeSpotTicker(vnokcoin.SYMBOL_LTC) - + self.subscribeSpotTicker(vnokcoin.SYMBOL_ETH) + self.subscribeSpotDepth(vnokcoin.SYMBOL_BTC, vnokcoin.DEPTH_20) self.subscribeSpotDepth(vnokcoin.SYMBOL_LTC, vnokcoin.DEPTH_20) - + self.subscribeSpotDepth(vnokcoin.SYMBOL_ETH, vnokcoin.DEPTH_20) + # 如果连接的是USD网站则订阅期货相关回报数据 if self.currency == vnokcoin.CURRENCY_USD: self.subscribeFutureTrades() @@ -346,10 +363,12 @@ class Api(vnokcoin.OkCoinApi): # USD_SPOT self.cbDict['ok_sub_spotusd_btc_ticker'] = self.onTicker self.cbDict['ok_sub_spotusd_ltc_ticker'] = self.onTicker - + self.cbDict['ok_sub_spotusd_eth_ticker'] = self.onTicker + self.cbDict['ok_sub_spotusd_btc_depth_20'] = self.onDepth self.cbDict['ok_sub_spotusd_ltc_depth_20'] = self.onDepth - + self.cbDict['ok_sub_spotusd_eth_depth_20'] = self.onDepth + self.cbDict['ok_spotusd_userinfo'] = self.onSpotUserInfo self.cbDict['ok_spotusd_orderinfo'] = self.onSpotOrderInfo @@ -362,10 +381,12 @@ class Api(vnokcoin.OkCoinApi): # CNY_SPOT self.cbDict['ok_sub_spotcny_btc_ticker'] = self.onTicker self.cbDict['ok_sub_spotcny_ltc_ticker'] = self.onTicker - + self.cbDict['ok_sub_spotcny_eth_ticker'] = self.onTicker + self.cbDict['ok_sub_spotcny_btc_depth_20'] = self.onDepth self.cbDict['ok_sub_spotcny_ltc_depth_20'] = self.onDepth - + self.cbDict['ok_sub_spotcny_eth_depth_20'] = self.onDepth + self.cbDict['ok_spotcny_userinfo'] = self.onSpotUserInfo self.cbDict['ok_spotcny_orderinfo'] = self.onSpotOrderInfo @@ -452,7 +473,7 @@ class Api(vnokcoin.OkCoinApi): funds = rawData['info']['funds'] # 持仓信息 - for symbol in ['btc', 'ltc', self.currency]: + for symbol in ['btc', 'ltc','eth', self.currency]: if symbol in funds['free']: pos = VtPositionData() pos.gatewayName = self.gatewayName @@ -485,7 +506,7 @@ class Api(vnokcoin.OkCoinApi): info = rawData['info'] # 持仓信息 - for symbol in ['btc', 'ltc', self.currency]: + for symbol in ['btc', 'ltc','eth', self.currency]: if symbol in info['free']: pos = VtPositionData() pos.gatewayName = self.gatewayName @@ -616,7 +637,8 @@ class Api(vnokcoin.OkCoinApi): contractList.append(self.generateSpecificContract(contract, BTC_CNY_SPOT)) contractList.append(self.generateSpecificContract(contract, LTC_CNY_SPOT)) - + contractList.append(self.generateSpecificContract(contract, ETH_CNY_SPOT)) + return contractList #---------------------------------------------------------------------- @@ -633,7 +655,8 @@ class Api(vnokcoin.OkCoinApi): contractList.append(self.generateSpecificContract(contract, BTC_USD_SPOT)) contractList.append(self.generateSpecificContract(contract, LTC_USD_SPOT)) - + contractList.append(self.generateSpecificContract(contract, ETH_USD_SPOT)) + # 期货 contract.productClass = PRODUCT_FUTURES @@ -643,6 +666,9 @@ class Api(vnokcoin.OkCoinApi): contractList.append(self.generateSpecificContract(contract, LTC_USD_THISWEEK)) contractList.append(self.generateSpecificContract(contract, LTC_USD_NEXTWEEK)) contractList.append(self.generateSpecificContract(contract, LTC_USD_QUARTER)) + contractList.append(self.generateSpecificContract(contract, ETH_USD_THISWEEK)) + contractList.append(self.generateSpecificContract(contract, ETH_USD_NEXTWEEK)) + contractList.append(self.generateSpecificContract(contract, ETH_USD_QUARTER)) return contractList diff --git a/vnpy/trader/gateway/qdpGateway/qdpGateway.py b/vnpy/trader/gateway/qdpGateway/qdpGateway.py index fe00f708..1c621518 100644 --- a/vnpy/trader/gateway/qdpGateway/qdpGateway.py +++ b/vnpy/trader/gateway/qdpGateway/qdpGateway.py @@ -15,6 +15,7 @@ from copy import copy from vnpy.api.qdp import MdApi, TdApi, defineDict from vnpy.trader.vtGateway import * +from vnpy.trader.vtFunction import getJsonPath # 以下为一些VT类型和QDP类型的映射字典 @@ -77,16 +78,14 @@ class QdpGateway(VtGateway): self.qryEnabled = False # 是否要启动循环查询 + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) + #---------------------------------------------------------------------- def connect(self): - """连接""" - # 载入json文件 - fileName = self.gatewayName + '_connect.json' - path = os.path.abspath(os.path.dirname(__file__)) - fileName = os.path.join(path, fileName) - + """连接""" try: - f = file(fileName) + f = file(self.filePath) except IOError: log = VtLogData() log.gatewayName = self.gatewayName diff --git a/vnpy/trader/gateway/sgitGateway/sgitGateway.py b/vnpy/trader/gateway/sgitGateway/sgitGateway.py index 4e009e10..d46501a3 100644 --- a/vnpy/trader/gateway/sgitGateway/sgitGateway.py +++ b/vnpy/trader/gateway/sgitGateway/sgitGateway.py @@ -14,7 +14,7 @@ from copy import copy from datetime import datetime from vnpy.api.sgit import MdApi, TdApi, defineDict -from vnpy.trader.vtFunction import getTempPath +from vnpy.trader.vtFunction import getTempPath, getJsonPath from vnpy.trader.vtGateway import * @@ -89,16 +89,14 @@ class SgitGateway(VtGateway): self.qryEnabled = False # 是否要启动循环查询 + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) + #---------------------------------------------------------------------- def connect(self): """连接""" - # 载入json文件 - fileName = self.gatewayName + '_connect.json' - path = os.path.abspath(os.path.dirname(__file__)) - fileName = os.path.join(path, fileName) - try: - f = file(fileName) + f = file(self.filePath) except IOError: log = VtLogData() log.gatewayName = self.gatewayName diff --git a/vnpy/trader/gateway/shzdGateway/shzdGateway.py b/vnpy/trader/gateway/shzdGateway/shzdGateway.py index a56d2d42..3ed9f305 100644 --- a/vnpy/trader/gateway/shzdGateway/shzdGateway.py +++ b/vnpy/trader/gateway/shzdGateway/shzdGateway.py @@ -17,6 +17,7 @@ from datetime import datetime from vnpy.api.shzd import ShzdApi from vnpy.trader.vtGateway import * +from vnpy.trader.vtFunction import getJsonPath # 以下为一些VT类型和SHZD类型的映射字典 @@ -34,10 +35,6 @@ directionMapReverse = {v: k for k, v in directionMap.items()} # 交易所类型映射 exchangeMap = {} -#exchangeMap[EXCHANGE_CFFEX] = 'CFFEX' -#exchangeMap[EXCHANGE_SHFE] = 'SHFE' -#exchangeMap[EXCHANGE_CZCE] = 'CZCE' -#exchangeMap[EXCHANGE_DCE] = 'DCE' exchangeMap[EXCHANGE_HKEX] = 'HKEX' exchangeMap[EXCHANGE_CME] = 'CME' exchangeMap[EXCHANGE_ICE] = 'ICE' @@ -72,16 +69,14 @@ class ShzdGateway(VtGateway): self.qryEnabled = False # 是否要启动循环查询 + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) + #---------------------------------------------------------------------- def connect(self): """连接""" - # 载入json文件 - fileName = self.gatewayName + '_connect.json' - path = os.path.abspath(os.path.dirname(__file__)) - fileName = os.path.join(path, fileName) - try: - f = file(fileName) + f = file(self.filePath) except IOError: log = VtLogData() log.gatewayName = self.gatewayName diff --git a/vnpy/trader/gateway/windGateway/windGateway.py b/vnpy/trader/gateway/windGateway/windGateway.py index 108643d1..1a872cdf 100644 --- a/vnpy/trader/gateway/windGateway/windGateway.py +++ b/vnpy/trader/gateway/windGateway/windGateway.py @@ -26,6 +26,9 @@ exchangeMap[EXCHANGE_CZCE] = 'CZC' exchangeMap[EXCHANGE_UNKNOWN] = '' exchangeMapReverse = {v:k for k,v in exchangeMap.items()} +# Wind接口相关事件 +EVENT_WIND_CONNECTREQ = 'eWindConnectReq' # Wind接口请求连接事件 + ######################################################################## class WindGateway(VtGateway): @@ -82,6 +85,9 @@ class WindGateway(VtGateway): # 而vt中的tick是完整更新,因此需要本地维护一个所有字段的快照 self.tickDict = {} + # 订阅请求缓存 + self.subscribeBufferDict = {} + self.registerEvent() #---------------------------------------------------------------------- @@ -97,8 +103,14 @@ class WindGateway(VtGateway): def subscribe(self, subscribeReq): """订阅行情""" windSymbol = '.'.join([subscribeReq.symbol, exchangeMap[subscribeReq.exchange]]) - data = self.w.wsq(windSymbol, self.wsqParam, func=self.wsqCallBack) - + + # 若已经连接则直接订阅 + if self.connected: + data = self.w.wsq(windSymbol, self.wsqParam, func=self.wsqCallBack) + # 否则缓存在字典中 + else: + self.subscribeBufferDict[windSymbol] = subscribeReq + #---------------------------------------------------------------------- def sendOrder(self, orderReq): """发单""" @@ -157,7 +169,7 @@ class WindGateway(VtGateway): self.tickDict[windSymbol] = tick dt = data.Times[0] - tick.time = dt.strftime('%H:%M:%S') + tick.time = dt.strftime('%H:%M:%S.%f') tick.date = dt.strftime('%Y%m%d') # 采用遍历的形式读取数值 @@ -183,6 +195,13 @@ class WindGateway(VtGateway): if not result.ErrorCode: log.logContent = u'Wind接口连接成功' + + self.connected = True + + # 发出缓存的订阅请求 + for req in self.subscribeBufferDict.values(): + self.subscribe(req) + self.subscribeBufferDict.clear() else: log.logContent = u'Wind接口连接失败,错误代码%d' %result.ErrorCode self.onLog(log) \ No newline at end of file diff --git a/vnpy/trader/gateway/xspeedGateway/xspeedGateway.py b/vnpy/trader/gateway/xspeedGateway/xspeedGateway.py index ae913eab..dd46ffef 100644 --- a/vnpy/trader/gateway/xspeedGateway/xspeedGateway.py +++ b/vnpy/trader/gateway/xspeedGateway/xspeedGateway.py @@ -11,6 +11,8 @@ from copy import copy from vnpy.api.xspeed import MdApi, TdApi, defineDict from vnpy.trader.vtGateway import * +from vnpy.trader.vtFunction import getJsonPath + # 以下为一些VT类型和XSPEED类型的映射字典 # 价格类型映射 @@ -76,16 +78,14 @@ class XspeedGateway(VtGateway): self.qryEnabled = False # 是否要启动循环查询 + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) + #---------------------------------------------------------------------- def connect(self): - """连接""" - # 载入json文件 - fileName = self.gatewayName + '_connect.json' - path = os.path.abspath(os.path.dirname(__file__)) - fileName = os.path.join(path, fileName) - + """连接""" try: - f = file(fileName) + f = file(self.filePath) except IOError: log = VtLogData() log.gatewayName = self.gatewayName diff --git a/vnpy/trader/ico/editor.ico b/vnpy/trader/ico/editor.ico new file mode 100644 index 00000000..e5227376 Binary files /dev/null and b/vnpy/trader/ico/editor.ico differ diff --git a/vnpy/trader/language/chinese/text.py b/vnpy/trader/language/chinese/text.py index 4627da96..6029c21a 100644 --- a/vnpy/trader/language/chinese/text.py +++ b/vnpy/trader/language/chinese/text.py @@ -121,6 +121,9 @@ RESTORE = u'还原窗口' ABOUT = u'关于' TEST = u'测试' CONNECT = u'连接' +EDIT_SETTING = u'编辑配置' +LOAD = u'读取' +SAVE = u'保存' CPU_MEMORY_INFO = u'CPU使用率:{cpu}% 内存使用率:{memory}%' CONFIRM_EXIT = u'确认退出?' diff --git a/vnpy/trader/language/english/text.py b/vnpy/trader/language/english/text.py index bf2c14b3..17b307d6 100644 --- a/vnpy/trader/language/english/text.py +++ b/vnpy/trader/language/english/text.py @@ -121,6 +121,9 @@ RESTORE = u'Restore Window' ABOUT = u'About' TEST = u'Test' CONNECT = u'Connect ' +EDIT_SETTING = 'Edit Setting' +LOAD = 'Load' +SAVE = 'Save' CPU_MEMORY_INFO = u'CPU Usage:{cpu}% Memory Usage:{memory}%' CONFIRM_EXIT = u'Confirm Exit?' diff --git a/vnpy/trader/uiBasicWidget.py b/vnpy/trader/uiBasicWidget.py index 0a793642..aff93177 100644 --- a/vnpy/trader/uiBasicWidget.py +++ b/vnpy/trader/uiBasicWidget.py @@ -7,11 +7,12 @@ import platform from collections import OrderedDict from vnpy.event import * -from vnpy.trader.vtEvent import * -from vnpy.trader.vtFunction import * -from vnpy.trader.vtGateway import * -from vnpy.trader import vtText -from vnpy.trader.uiQt import QtGui, QtWidgets, QtCore, BASIC_FONT +from .vtEvent import * +from .vtFunction import * +from .vtGateway import * +from . import vtText +from .uiQt import QtGui, QtWidgets, QtCore, BASIC_FONT +from .vtFunction import jsonPathDict COLOR_RED = QtGui.QColor('red') @@ -1229,3 +1230,92 @@ class ContractManager(QtWidgets.QWidget): self.monitor.refresh() + +######################################################################## +class SettingEditor(QtWidgets.QWidget): + """配置编辑器""" + + #---------------------------------------------------------------------- + def __init__(self, mainEngine, parent=None): + """Constructor""" + super(SettingEditor, self).__init__(parent) + + self.mainEngine = mainEngine + self.currentFileName = '' + + self.initUi() + + #---------------------------------------------------------------------- + def initUi(self): + """初始化界面""" + self.setWindowTitle(vtText.EDIT_SETTING) + + self.comboFileName = QtWidgets.QComboBox() + self.comboFileName.addItems(jsonPathDict.keys()) + + buttonLoad = QtWidgets.QPushButton(vtText.LOAD) + buttonSave = QtWidgets.QPushButton(vtText.SAVE) + buttonLoad.clicked.connect(self.loadSetting) + buttonSave.clicked.connect(self.saveSetting) + + self.editSetting = QtWidgets.QTextEdit() + self.labelPath = QtWidgets.QLabel() + + hbox = QtWidgets.QHBoxLayout() + hbox.addWidget(self.comboFileName) + hbox.addWidget(buttonLoad) + hbox.addWidget(buttonSave) + hbox.addStretch() + + vbox = QtWidgets.QVBoxLayout() + vbox.addLayout(hbox) + vbox.addWidget(self.editSetting) + vbox.addWidget(self.labelPath) + + self.setLayout(vbox) + + #---------------------------------------------------------------------- + def loadSetting(self): + """加载配置""" + self.currentFileName = str(self.comboFileName.currentText()) + filePath = jsonPathDict[self.currentFileName] + self.labelPath.setText(filePath) + + with open(filePath) as f: + self.editSetting.clear() + + for line in f: + line = line.replace('\n', '') # 移除换行符号 + line = line.decode('UTF-8') + self.editSetting.append(line) + + #---------------------------------------------------------------------- + def saveSetting(self): + """保存配置""" + if not self.currentFileName: + return + + filePath = jsonPathDict[self.currentFileName] + + with open(filePath, 'w') as f: + content = self.editSetting.toPlainText() + content = content.encode('UTF-8') + f.write(content) + + #---------------------------------------------------------------------- + def show(self): + """显示""" + # 更新配置文件下拉框 + self.comboFileName.clear() + self.comboFileName.addItems(jsonPathDict.keys()) + + # 显示界面 + super(SettingEditor, self).show() + + + + + + + + diff --git a/vnpy/trader/uiMainWindow.py b/vnpy/trader/uiMainWindow.py index 27d92ad9..2fd2769f 100644 --- a/vnpy/trader/uiMainWindow.py +++ b/vnpy/trader/uiMainWindow.py @@ -1,6 +1,7 @@ # encoding: UTF-8 import psutil +import traceback from vnpy.trader.vtFunction import loadIconPath from vnpy.trader.vtGlobal import globalSetting @@ -116,6 +117,7 @@ class MainWindow(QtWidgets.QMainWindow): # 帮助 helpMenu = menubar.addMenu(vtText.HELP) helpMenu.addAction(self.createAction(vtText.CONTRACT_SEARCH, self.openContract, loadIconPath('contract.ico'))) + helpMenu.addAction(self.createAction(vtText.EDIT_SETTING, self.openSettingEditor, loadIconPath('editor.ico'))) helpMenu.addSeparator() helpMenu.addAction(self.createAction(vtText.RESTORE, self.restoreWindow, loadIconPath('restore.ico'))) helpMenu.addAction(self.createAction(vtText.ABOUT, self.openAbout, loadIconPath('about.ico'))) @@ -218,6 +220,15 @@ class MainWindow(QtWidgets.QMainWindow): except KeyError: self.widgetDict['contractM'] = ContractManager(self.mainEngine) self.widgetDict['contractM'].show() + + #---------------------------------------------------------------------- + def openSettingEditor(self): + """打开配置编辑""" + try: + self.widgetDict['settingEditor'].show() + except KeyError: + self.widgetDict['settingEditor'] = SettingEditor(self.mainEngine) + self.widgetDict['settingEditor'].show() #---------------------------------------------------------------------- def closeEvent(self, event): @@ -257,18 +268,25 @@ class MainWindow(QtWidgets.QMainWindow): #---------------------------------------------------------------------- def loadWindowSettings(self, settingName): """载入窗口设置""" - settings = QtCore.QSettings('vn.trader', settingName) - # 这里由于PyQt4的版本不同,settings.value('state')调用返回的结果可能是: - # 1. None(初次调用,注册表里无相应记录,因此为空) - # 2. QByteArray(比较新的PyQt4) - # 3. QVariant(以下代码正确执行所需的返回结果) - # 所以为了兼容考虑,这里加了一个try...except,如果是1、2的情况就pass - # 可能导致主界面的设置无法载入(每次退出时的保存其实是成功了) - try: - self.restoreState(settings.value('state').toByteArray()) - self.restoreGeometry(settings.value('geometry').toByteArray()) - except AttributeError: - pass + settings = QtCore.QSettings('vn.trader', settingName) + state = settings.value('state') + geometry = settings.value('geometry') + + # 尚未初始化 + if state is None: + return + # 老版PyQt + elif isinstance(state, QtCore.QVariant): + self.restoreState(state.toByteArray()) + self.restoreGeometry(geometry.toByteArray()) + # 新版PyQt + elif isinstance(state, QtCore.QByteArray): + self.restoreState(state) + self.restoreGeometry(geometry) + # 异常 + else: + content = u'载入窗口配置异常,请检查' + self.mainEngine.writeLog(content) #---------------------------------------------------------------------- def restoreWindow(self): diff --git a/vnpy/trader/vtEngine.py b/vnpy/trader/vtEngine.py index 70b087e5..ab52c066 100644 --- a/vnpy/trader/vtEngine.py +++ b/vnpy/trader/vtEngine.py @@ -5,7 +5,7 @@ import shelve from collections import OrderedDict from datetime import datetime -from pymongo import MongoClient +from pymongo import MongoClient, ASCENDING from pymongo.errors import ConnectionFailure from vnpy.event import Event @@ -209,12 +209,17 @@ class MainEngine(object): self.writeLog(text.DATA_INSERT_FAILED) #---------------------------------------------------------------------- - def dbQuery(self, dbName, collectionName, d): + def dbQuery(self, dbName, collectionName, d, sortKey='', sortDirection=ASCENDING): """从MongoDB中读取数据,d是查询要求,返回的是数据库查询的指针""" if self.dbClient: db = self.dbClient[dbName] collection = db[collectionName] - cursor = collection.find(d) + + if sortKey: + cursor = collection.find(d).sort(sortKey, sortDirection) # 对查询出来的数据进行排序 + else: + cursor = collection.find(d) + if cursor: return list(cursor) else: diff --git a/vnpy/trader/vtEvent.py b/vnpy/trader/vtEvent.py index b26175fb..4c5c67ef 100644 --- a/vnpy/trader/vtEvent.py +++ b/vnpy/trader/vtEvent.py @@ -17,14 +17,4 @@ EVENT_ORDER = 'eOrder.' # 报单回报事件 EVENT_POSITION = 'ePosition.' # 持仓回报事件 EVENT_ACCOUNT = 'eAccount.' # 账户回报事件 EVENT_CONTRACT = 'eContract.' # 合约基础信息回报事件 -EVENT_ERROR = 'eError.' # 错误回报事件 - -# CTA模块相关 -EVENT_CTA_LOG = 'eCtaLog' # CTA相关的日志事件 -EVENT_CTA_STRATEGY = 'eCtaStrategy.' # CTA策略状态变化事件 - -# 行情记录模块相关 -EVENT_DATARECORDER_LOG = 'eDataRecorderLog' # 行情记录日志更新事件 - -# Wind接口相关 -EVENT_WIND_CONNECTREQ = 'eWindConnectReq' # Wind接口请求连接事件 \ No newline at end of file +EVENT_ERROR = 'eError.' # 错误回报事件 \ No newline at end of file diff --git a/vnpy/trader/vtFunction.py b/vnpy/trader/vtFunction.py index 189d1d82..acd7405c 100644 --- a/vnpy/trader/vtFunction.py +++ b/vnpy/trader/vtFunction.py @@ -63,6 +63,29 @@ def getTempPath(name): path = os.path.join(tempPath, name) return path + + +# JSON配置文件路径 +jsonPathDict = {} + +#---------------------------------------------------------------------- +def getJsonPath(name, moduleFile): + """ + 获取JSON配置文件的路径: + 1. 优先从当前工作目录查找JSON文件 + 2. 若无法找到则前往模块所在目录查找 + """ + currentFolder = os.getcwd() + currentJsonPath = os.path.join(currentFolder, name) + if os.path.isfile(currentJsonPath): + jsonPathDict[name] = currentJsonPath + return currentJsonPath + + moduleFolder = os.path.abspath(os.path.dirname(moduleFile)) + moduleJsonPath = os.path.join(moduleFolder, '.', name) + jsonPathDict[name] = moduleJsonPath + return moduleJsonPath + diff --git a/vnpy/trader/vtGlobal.py b/vnpy/trader/vtGlobal.py index ab5a6f8d..0b4bc72c 100644 --- a/vnpy/trader/vtGlobal.py +++ b/vnpy/trader/vtGlobal.py @@ -7,14 +7,13 @@ import os import traceback import json - +from vtFunction import getJsonPath globalSetting = {} # 全局配置字典 settingFileName = "VT_setting.json" -path = os.path.abspath(os.path.dirname(__file__)) -settingFileName = os.path.join(path, settingFileName) +settingFilePath = getJsonPath(settingFileName, __file__) try: f = file(settingFileName)