From 9870e34eb05aadc3394033345e4cdb6d01d4c199 Mon Sep 17 00:00:00 2001 From: chenxy123 Date: Wed, 7 Jun 2017 22:00:14 +0800 Subject: [PATCH 01/19] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=AD=E4=BF=A1?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E8=8B=B1=E6=96=87=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README-en.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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** From d89245a1c9654ad56d2bc3f6b1c6b29dd80c45c6 Mon Sep 17 00:00:00 2001 From: bukeyi Date: Fri, 9 Jun 2017 18:25:43 +0800 Subject: [PATCH 02/19] update new pip source --- docker/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From 8c167c392e980dbdb656f513e9ec948e88cf9b3b Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Mon, 12 Jun 2017 17:14:34 +0800 Subject: [PATCH 03/19] =?UTF-8?q?=E6=B7=BB=E5=8A=A0VnTrader=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E4=BB=B7=E5=B7=AE=E4=BA=A4=E6=98=93=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=EF=BC=8C=E5=AE=8C=E6=88=90=E4=BB=B7=E5=B7=AE=E8=A1=8C=E6=83=85?= =?UTF-8?q?=E7=9A=84=E8=87=AA=E5=8A=A8=E8=AE=A1=E7=AE=97=E5=92=8C=E6=98=BE?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/VnTrader/run.py | 3 +- vnpy/trader/app/ctaStrategy/ctaBase.py | 4 + vnpy/trader/app/dataRecorder/drBase.py | 2 + vnpy/trader/app/spreadTrading/ST_setting.json | 64 +++++++ vnpy/trader/app/spreadTrading/__init__.py | 10 + vnpy/trader/app/spreadTrading/st.ico | Bin 0 -> 45118 bytes vnpy/trader/app/spreadTrading/stBase.py | 162 ++++++++++++++++ vnpy/trader/app/spreadTrading/stEngine.py | 176 ++++++++++++++++++ vnpy/trader/app/spreadTrading/uiStWidget.py | 97 ++++++++++ vnpy/trader/gateway/ctpGateway/ctpGateway.py | 2 - .../trader/gateway/windGateway/windGateway.py | 3 + vnpy/trader/vtEvent.py | 12 +- vnpy/trader/vtFunction.py | 15 ++ 13 files changed, 536 insertions(+), 14 deletions(-) create mode 100644 vnpy/trader/app/spreadTrading/ST_setting.json create mode 100644 vnpy/trader/app/spreadTrading/__init__.py create mode 100644 vnpy/trader/app/spreadTrading/st.ico create mode 100644 vnpy/trader/app/spreadTrading/stBase.py create mode 100644 vnpy/trader/app/spreadTrading/stEngine.py create mode 100644 vnpy/trader/app/spreadTrading/uiStWidget.py diff --git a/examples/VnTrader/run.py b/examples/VnTrader/run.py index e174bd53..22c5a54a 100644 --- a/examples/VnTrader/run.py +++ b/examples/VnTrader/run.py @@ -18,7 +18,7 @@ from vnpy.trader.gateway import (ctpGateway, femasGateway, xspeedGateway, # 加载上层应用 from vnpy.trader.app import (riskManager, dataRecorder, - ctaStrategy) + ctaStrategy, spreadTrading) #---------------------------------------------------------------------- @@ -45,6 +45,7 @@ def main(): me.addApp(riskManager) me.addApp(dataRecorder) me.addApp(ctaStrategy) + me.addApp(spreadTrading) # 创建主窗口 mw = MainWindow(me, ee) 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/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/spreadTrading/ST_setting.json b/vnpy/trader/app/spreadTrading/ST_setting.json new file mode 100644 index 00000000..b2edf2c1 --- /dev/null +++ b/vnpy/trader/app/spreadTrading/ST_setting.json @@ -0,0 +1,64 @@ +[ + { + "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 0000000000000000000000000000000000000000..a66c245e0dc6c55842f923177515889ca5b9ce4e GIT binary patch literal 45118 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 literal 0 HcmV?d00001 diff --git a/vnpy/trader/app/spreadTrading/stBase.py b/vnpy/trader/app/spreadTrading/stBase.py new file mode 100644 index 00000000..9297b764 --- /dev/null +++ b/vnpy/trader/app/spreadTrading/stBase.py @@ -0,0 +1,162 @@ +# 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.' + + + +######################################################################## +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.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..d5f38a9b --- /dev/null +++ b/vnpy/trader/app/spreadTrading/stEngine.py @@ -0,0 +1,176 @@ +# encoding: UTF-8 + +import json +from copy import copy + +from vnpy.event import Event +from vnpy.trader.vtFunction import getJsonPath +from vnpy.trader.vtEvent import EVENT_TICK, EVENT_TRADE, EVENT_POSITION +from vnpy.trader.vtObject import VtSubscribeReq + +from .stBase import StLeg, StSpread, EVENT_SPREADTRADING_TICK + + +######################################################################## +class StEngine(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): + """加载配置""" + with open(self.settingFilePath) as f: + l = json.load(f) + + for setting in l: + self.createSpread(setting) + + #---------------------------------------------------------------------- + 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 = activeSetting['vtSymbol'] + activeLeg.ratio = activeSetting['ratio'] + activeLeg.multiplier = activeSetting['multiplier'] + activeLeg.payup = 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 = d['vtSymbol'] + passiveLeg.ratio = d['ratio'] + passiveLeg.multiplier = d['multiplier'] + passiveLeg.payup = d['payup'] + + spread.addPassiveLeg(passiveLeg) + self.legDict[passiveLeg.vtSymbol] = passiveLeg + self.vtSymbolSpreadDict[passiveLeg.vtSymbol] = spread + + self.subscribeMarketData(passiveLeg.vtSymbol) + + # 初始化价差 + spread.initSpread() + + # 返回结果 + 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() + + # 推送价差更新 + newSpread = copy(spread) + event = Event(EVENT_SPREADTRADING_TICK) + event.dict_['data'] = newSpread + self.eventEngine.put(event) + + #---------------------------------------------------------------------- + def processTradeEvent(self, event): + """""" + pass + + #---------------------------------------------------------------------- + def processPositionEvent(self, event): + """""" + pass + + #---------------------------------------------------------------------- + def registerEvent(self): + """""" + self.eventEngine.register(EVENT_TICK, self.processTickEvent) + self.eventEngine.register(EVENT_TRADE, self.processTradeEvent) + self.eventEngine.register(EVENT_POSITION, self.processPositionEvent) + + #---------------------------------------------------------------------- + def subscribeMarketData(self, vtSymbol): + """订阅行情""" + contract = self.mainEngine.getContract(vtSymbol) + if not contract: + return + + req = VtSubscribeReq() + req.symbol = contract.symbol + req.exchange = contract.exchange + + self.mainEngine.subscribe(req, contract.gatewayName) + + #---------------------------------------------------------------------- + def stop(self): + """停止""" + pass + + \ 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..c0cd5b21 --- /dev/null +++ b/vnpy/trader/app/spreadTrading/uiStWidget.py @@ -0,0 +1,97 @@ +# encoding: UTF-8 + +from collections import OrderedDict + +from vnpy.trader.uiQt import QtWidgets +from vnpy.trader.uiBasicWidget import (BasicMonitor, BasicCell, + AskCell, BidCell, BASIC_FONT) + +from .stBase import EVENT_SPREADTRADING_TICK + + + +######################################################################## +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 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'价差交易') + + # 创建按钮 + buttonLoadSetting = QtWidgets.QPushButton(u'加载配置') + + buttonLoadSetting.clicked.connect(self.stEngine.loadSetting) + + # 创建组件 + tickMonitor = StTickMonitor(self.mainEngine, self.eventEngine) + + # 设置布局 + hbox = QtWidgets.QHBoxLayout() + hbox.addWidget(buttonLoadSetting) + hbox.addStretch() + + vbox = QtWidgets.QVBoxLayout() + vbox.addLayout(hbox) + vbox.addWidget(tickMonitor) + + self.setLayout(vbox) + + #---------------------------------------------------------------------- + def show(self): + """重载显示""" + self.showMaximized() + + + + + + \ No newline at end of file diff --git a/vnpy/trader/gateway/ctpGateway/ctpGateway.py b/vnpy/trader/gateway/ctpGateway/ctpGateway.py index 619eacea..2294cd21 100644 --- a/vnpy/trader/gateway/ctpGateway/ctpGateway.py +++ b/vnpy/trader/gateway/ctpGateway/ctpGateway.py @@ -91,8 +91,6 @@ class CtpGateway(VtGateway): self.tdConnected = False # 交易API连接状态 self.qryEnabled = False # 循环查询 - - self.requireAuthentication = False #---------------------------------------------------------------------- def connect(self): diff --git a/vnpy/trader/gateway/windGateway/windGateway.py b/vnpy/trader/gateway/windGateway/windGateway.py index 108643d1..d1307841 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): 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..32bc99a4 100644 --- a/vnpy/trader/vtFunction.py +++ b/vnpy/trader/vtFunction.py @@ -64,5 +64,20 @@ def getTempPath(name): path = os.path.join(tempPath, name) return path +#---------------------------------------------------------------------- +def getJsonPath(name, moduleFile): + """ + 获取JSON配置文件的路径: + 1. 优先从当前工作目录查找JSON文件 + 2. 若无法找到则前往模块所在目录查找 + """ + currentFolder = os.getcwd() + currentJsonPath = os.path.join(currentFolder, name) + if os.path.isfile(currentJsonPath): + return currentJsonPath + + moduleFolder = os.path.abspath(os.path.dirname(moduleFile)) + moduleJsonPath = os.path.join(moduleFolder, '.', name) + return moduleJsonPath From e6762a63e3e289cebbd4d8e2cbd9cfcfaf7e7302 Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Tue, 13 Jun 2017 14:03:16 +0800 Subject: [PATCH 04/19] =?UTF-8?q?=E5=A2=9E=E5=8A=A0wind=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=9C=A8=E6=9C=AA=E8=BF=9E=E6=8E=A5=E6=97=B6=E7=9A=84=E8=AE=A2?= =?UTF-8?q?=E9=98=85=E8=AF=B7=E6=B1=82=E7=BC=93=E5=AD=98=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../trader/gateway/windGateway/windGateway.py | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/vnpy/trader/gateway/windGateway/windGateway.py b/vnpy/trader/gateway/windGateway/windGateway.py index d1307841..279eac5c 100644 --- a/vnpy/trader/gateway/windGateway/windGateway.py +++ b/vnpy/trader/gateway/windGateway/windGateway.py @@ -85,6 +85,9 @@ class WindGateway(VtGateway): # 而vt中的tick是完整更新,因此需要本地维护一个所有字段的快照 self.tickDict = {} + # 订阅请求缓存 + self.subscribeBufferDict = {} + self.registerEvent() #---------------------------------------------------------------------- @@ -100,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): """发单""" @@ -160,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') # 采用遍历的形式读取数值 @@ -186,6 +195,11 @@ class WindGateway(VtGateway): if not result.ErrorCode: log.logContent = u'Wind接口连接成功' + + # 发出缓存的订阅请求 + 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 From e8fbab872d288fa09857a63e6afdc37e64c3d2ce Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Wed, 14 Jun 2017 13:41:59 +0800 Subject: [PATCH 05/19] =?UTF-8?q?=E4=BF=AE=E5=A4=8DCTA=E4=B8=AD=E7=9A=84bu?= =?UTF-8?q?g?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vnpy/trader/app/ctaStrategy/uiCtaWidget.py | 3 ++- vnpy/trader/gateway/windGateway/windGateway.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/vnpy/trader/app/ctaStrategy/uiCtaWidget.py b/vnpy/trader/app/ctaStrategy/uiCtaWidget.py index f1b077ae..d9e8531b 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 ######################################################################## diff --git a/vnpy/trader/gateway/windGateway/windGateway.py b/vnpy/trader/gateway/windGateway/windGateway.py index 279eac5c..1a872cdf 100644 --- a/vnpy/trader/gateway/windGateway/windGateway.py +++ b/vnpy/trader/gateway/windGateway/windGateway.py @@ -196,6 +196,8 @@ class WindGateway(VtGateway): if not result.ErrorCode: log.logContent = u'Wind接口连接成功' + self.connected = True + # 发出缓存的订阅请求 for req in self.subscribeBufferDict.values(): self.subscribe(req) From 7ed32dca4b0b7020f7493cf9a90b36d8fd7c59ab Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Thu, 15 Jun 2017 14:43:49 +0800 Subject: [PATCH 06/19] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BB=B7=E5=B7=AE?= =?UTF-8?q?=E4=BA=A4=E6=98=93=E7=9A=84=E6=8C=81=E4=BB=93=E5=92=8C=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vnpy/trader/app/ctaStrategy/uiCtaWidget.py | 15 +-- vnpy/trader/app/spreadTrading/stBase.py | 2 + vnpy/trader/app/spreadTrading/stEngine.py | 123 +++++++++++++++++--- vnpy/trader/app/spreadTrading/uiStWidget.py | 79 ++++++++++--- 4 files changed, 178 insertions(+), 41 deletions(-) diff --git a/vnpy/trader/app/ctaStrategy/uiCtaWidget.py b/vnpy/trader/app/ctaStrategy/uiCtaWidget.py index d9e8531b..62366a89 100644 --- a/vnpy/trader/app/ctaStrategy/uiCtaWidget.py +++ b/vnpy/trader/app/ctaStrategy/uiCtaWidget.py @@ -268,13 +268,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/spreadTrading/stBase.py b/vnpy/trader/app/spreadTrading/stBase.py index 9297b764..64f0f00f 100644 --- a/vnpy/trader/app/spreadTrading/stBase.py +++ b/vnpy/trader/app/spreadTrading/stBase.py @@ -11,6 +11,8 @@ from vnpy.trader.vtConstant import (EMPTY_INT, EMPTY_FLOAT, EVENT_SPREADTRADING_TICK = 'eSpreadTradingTick.' +EVENT_SPREADTRADING_POS = 'eSpreadTradingPos.' +EVENT_SPREADTRADING_LOG = 'eSpreadTradingLog' diff --git a/vnpy/trader/app/spreadTrading/stEngine.py b/vnpy/trader/app/spreadTrading/stEngine.py index d5f38a9b..4ce43a52 100644 --- a/vnpy/trader/app/spreadTrading/stEngine.py +++ b/vnpy/trader/app/spreadTrading/stEngine.py @@ -1,14 +1,18 @@ # encoding: UTF-8 import json +import traceback from copy import copy from vnpy.event import Event from vnpy.trader.vtFunction import getJsonPath from vnpy.trader.vtEvent import EVENT_TICK, EVENT_TRADE, EVENT_POSITION -from vnpy.trader.vtObject import VtSubscribeReq +from vnpy.trader.vtObject import VtSubscribeReq, VtLogData +from vnpy.trader.vtConstant import (DIRECTION_LONG, DIRECTION_SHORT, + OFFSET_OPEN, OFFSET_CLOSE) -from .stBase import StLeg, StSpread, EVENT_SPREADTRADING_TICK +from .stBase import (StLeg, StSpread, EVENT_SPREADTRADING_TICK, + EVENT_SPREADTRADING_POS, EVENT_SPREADTRADING_LOG) ######################################################################## @@ -33,11 +37,18 @@ class StEngine(object): #---------------------------------------------------------------------- def loadSetting(self): """加载配置""" - with open(self.settingFilePath) as f: - l = json.load(f) - - for setting in l: - self.createSpread(setting) + 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): @@ -53,7 +64,7 @@ class StEngine(object): # 检查价差重名 if setting['name'] in self.spreadDict: - msg = u'%s价差重名' %setting['name'] + msg = u'%s价差存在重名' %setting['name'] return result, msg # 检查腿是否已使用 @@ -132,21 +143,89 @@ class StEngine(object): spread = self.vtSymbolSpreadDict[tick.vtSymbol] spread.calculatePrice() - # 推送价差更新 + # 推送价差行情更新 newSpread = copy(spread) - event = Event(EVENT_SPREADTRADING_TICK) - event.dict_['data'] = newSpread - self.eventEngine.put(event) + + event1 = Event(EVENT_SPREADTRADING_TICK+spread.name) + event1.dict_['data'] = newSpread + self.eventEngine.put(event1) + + event2 = Event(EVENT_SPREADTRADING_TICK) + event2.dict_['data'] = newSpread + self.eventEngine.put(event2) #---------------------------------------------------------------------- def processTradeEvent(self, event): - """""" - pass + """处理成交推送""" + # 检查成交是否需要处理 + trade = event.dict_['data'] + if trade.vtSymbol not in self.legDict: + return + + # 更新腿持仓 + leg = self.legDict[trade.vtSymbol] + direction = trade.direction + offset = trade.offst + + 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() + + # 推送价差持仓更新 + newSpread = copy(spread) + + event1 = Event(EVENT_SPREADTRADING_POS+spread.name) + event1.dict_['data'] = newSpread + self.eventEngine.put(event1) + + event2 = Event(EVENT_SPREADTRADING_POS) + event2.dict_['data'] = newSpread + self.eventEngine.put(event2) #---------------------------------------------------------------------- def processPositionEvent(self, event): - """""" - pass + """处理持仓推送""" + # 检查持仓是否需要处理 + pos = event.dict_['data'] + if pos.vtSymbol not in self.legDict: + return + + # 更新腿持仓 + leg = self.legDict[trade.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[trade.vtSymbol] + spread.calculatePos() + + # 推送价差持仓更新 + newSpread = copy(spread) + + event1 = Event(EVENT_SPREADTRADING_POS+spread.name) + event1.dict_['data'] = newSpread + self.eventEngine.put(event1) + + event2 = Event(EVENT_SPREADTRADING_POS) + event2.dict_['data'] = newSpread + self.eventEngine.put(event2) #---------------------------------------------------------------------- def registerEvent(self): @@ -160,7 +239,7 @@ class StEngine(object): """订阅行情""" contract = self.mainEngine.getContract(vtSymbol) if not contract: - return + self.writeLog(u'订阅行情失败,找不到该合约%s' %vtSymbol) req = VtSubscribeReq() req.symbol = contract.symbol @@ -168,6 +247,16 @@ class StEngine(object): 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 stop(self): """停止""" diff --git a/vnpy/trader/app/spreadTrading/uiStWidget.py b/vnpy/trader/app/spreadTrading/uiStWidget.py index c0cd5b21..9244fc4d 100644 --- a/vnpy/trader/app/spreadTrading/uiStWidget.py +++ b/vnpy/trader/app/spreadTrading/uiStWidget.py @@ -3,23 +3,23 @@ from collections import OrderedDict from vnpy.trader.uiQt import QtWidgets -from vnpy.trader.uiBasicWidget import (BasicMonitor, BasicCell, +from vnpy.trader.uiBasicWidget import (BasicMonitor, BasicCell, PnlCell, AskCell, BidCell, BASIC_FONT) -from .stBase import EVENT_SPREADTRADING_TICK +from .stBase import (EVENT_SPREADTRADING_TICK, EVENT_SPREADTRADING_POS, + EVENT_SPREADTRADING_LOG) ######################################################################## 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} @@ -30,22 +30,60 @@ class StTickMonitor(BasicMonitor): 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(BasicMonitor): + """价差日志监控""" + + #---------------------------------------------------------------------- + def __init__(self, mainEngine, eventEngine, parent=None): + """Constructor""" + super(StLogMonitor, 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_LOG) + self.setFont(BASIC_FONT) + + self.initTable() + self.registerEvent() + + ######################################################################## class StManager(QtWidgets.QWidget): """""" @@ -73,15 +111,22 @@ class StManager(QtWidgets.QWidget): # 创建组件 tickMonitor = StTickMonitor(self.mainEngine, self.eventEngine) + posMonitor = StPosMonitor(self.mainEngine, self.eventEngine) + logMonitor = StLogMonitor(self.mainEngine, self.eventEngine) # 设置布局 - hbox = QtWidgets.QHBoxLayout() - hbox.addWidget(buttonLoadSetting) - hbox.addStretch() + hbox1 = QtWidgets.QHBoxLayout() + hbox1.addWidget(buttonLoadSetting) + hbox1.addStretch() + + hbox2 = QtWidgets.QHBoxLayout() + hbox2.addWidget(tickMonitor) + hbox2.addWidget(posMonitor) vbox = QtWidgets.QVBoxLayout() - vbox.addLayout(hbox) - vbox.addWidget(tickMonitor) + vbox.addLayout(hbox1) + vbox.addLayout(hbox2) + vbox.addWidget(logMonitor) self.setLayout(vbox) From a2a075ba6ad68d3f11fb108ed70f4ff24f588620 Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Wed, 21 Jun 2017 17:19:21 +0800 Subject: [PATCH 07/19] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BB=B7=E5=B7=AE?= =?UTF-8?q?=E7=AE=97=E6=B3=95=E5=BC=95=E6=93=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vnpy/rpc/vnrpc.py | 4 +- vnpy/trader/app/ctaStrategy/ctaBacktesting.py | 5 +- vnpy/trader/app/spreadTrading/stAlgo.py | 294 ++++++++++++++++++ vnpy/trader/app/spreadTrading/stEngine.py | 185 +++++++++-- 4 files changed, 466 insertions(+), 22 deletions(-) create mode 100644 vnpy/trader/app/spreadTrading/stAlgo.py 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..01b29280 100644 --- a/vnpy/trader/app/ctaStrategy/ctaBacktesting.py +++ b/vnpy/trader/app/ctaStrategy/ctaBacktesting.py @@ -812,7 +812,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 +929,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 +939,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/spreadTrading/stAlgo.py b/vnpy/trader/app/spreadTrading/stAlgo.py new file mode 100644 index 00000000..ba6e1a86 --- /dev/null +++ b/vnpy/trader/app/spreadTrading/stAlgo.py @@ -0,0 +1,294 @@ +# 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 = 'longshort' + MODE_LONGONLY = 'long' + MODE_SHORTONLY = 'short' + + #---------------------------------------------------------------------- + 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 + + + +######################################################################## +class SniperAlgo(StAlgoTemplate): + """狙击算法(市价委托)""" + FINISHED_STATUS = [STATUS_ALLTRADED, STATUS_CANCELLED, STATUS_REJECTED] + + #---------------------------------------------------------------------- + def __init__(self, algoEngine): + """Constructor""" + super(SniperAlgo, self).__init__(algoEngine) + + self.algoName = u'Sniper' + 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 + + #---------------------------------------------------------------------- + def updateSpreadTick(self, spread): + """价差行情更新""" + self.spread = spread + + #---------------------------------------------------------------------- + def updateSpreadPos(self, spread): + """价差持仓更新""" + self.spread = spread + + #---------------------------------------------------------------------- + def updateTrade(self, trade): + """成交更新""" + if not self.active: + return + + if trade.vtSymbol == self.activeVtSymbol: + self.newActiveLegTrade(trade) + else: + self.newPassiveLegTrade(trade) + + #---------------------------------------------------------------------- + def updateOrder(self, order): + """委托更新""" + if not self.active: + return + + # 只处理完成委托 + 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): + """计时更新""" + self.hedgeCount += 1 + + # 计时到达对冲间隔后,则对尚未成交的全部被动腿委托全部撤单 + # 收到撤单回报后,会自动发送新的对冲委托 + if self.hedgeCount > self.hedgeInterval: + self.cancelAllPassiveLegOrders() + self.hedgeCount = 0 + + #---------------------------------------------------------------------- + def start(self): + """启动""" + raise NotImplementedError + + #---------------------------------------------------------------------- + def stop(self): + """停止""" + self.active = False + + self.hedgingTaskDict.clear() + self.cancelAllOrders() + + #---------------------------------------------------------------------- + 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 == DIRECTION_LONG: + spreadVolume = min(spread.askVolume, + self.maxPosSize - spread.netPos, + self.maxOrderSize) + else: + spreadVolume = min(spread.bidVolume, + self.maxPosSize + spread.netPos, + self.maxOrderSize) + + if spreadVolume <= 0: + return + + leg = self.legDict[self.activeVtSymbol] + legVolume = spreadVolume * leg.ratio + + self.sendLegOrder(leg, legVolume) + + #---------------------------------------------------------------------- + def hedgePassiveLeg(self, vtSymbol): + """被动腿对冲""" + if vtSymbol not in self.hedgingTaskDict: + return + legVolume = self.hedgingTaskDict[vtSymbol] + + leg = self.legDict[vtSymbol] + + self.sendLegOrder(leg, legVolume) + + #---------------------------------------------------------------------- + def hedgeAllPassiveLegs(self): + """执行所有被动腿对冲""" + for vtSymbol in self.hedgingTaskDict.keys(): + self.hedgePassiveLeg(vtSymbol) + + #---------------------------------------------------------------------- + def newActiveLegTrade(self, trade): + """新的主动腿成交""" + spread = self.spread + + # 计算主动腿成交后,对应的价差仓位 + activeRatio = spread.activeLeg.ratio + spreadVolume = round(trade.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 + + #---------------------------------------------------------------------- + def newPassiveLegTrade(self, trade): + """新的被动腿成交""" + if trade.vtSymbol in self.hedgingTaskDict: + # 计算完成的对冲数量 + if trade.direction == DIRECTION_LONG: + hedgedVolume = trade.volume + else: + hedgedVolume = -trade.volume + + # 计算剩余尚未完成的数量 + self.hedgingTaskDict[trade.vtSymbol] -= hedgedVolume + + # 如果已全部完成,则从字典中移除 + if not self.hedgingTaskDict[trade.vtSymbol]: + del self.hedgingTaskDict[trade.vtSymbol] + + #---------------------------------------------------------------------- + def cancelLegOrder(self, vtSymbol): + """撤销某条腿的委托""" + if vtSymbol not in self.legOrderDict: + return + + orderList = self.legOrderDict[vtSymbol] + + for vtOrderID in orderList: + self.algoEngine.cancelOrder(vtOrderID) + + #---------------------------------------------------------------------- + def cancelAllOrders(self): + """撤销全部委托""" + for orderList in self.legOrderDict.values(): + for vtOrderID in orderList: + self.algoEngine.cancelOrder(vtOrderID) + + #---------------------------------------------------------------------- + def cancelAllPassiveLegOrders(self): + """撤销全部被动腿委托""" + for vtSymbol in self.passiveVtSymbols: + self.cancelLegOrder(vtSymbol) \ No newline at end of file diff --git a/vnpy/trader/app/spreadTrading/stEngine.py b/vnpy/trader/app/spreadTrading/stEngine.py index 4ce43a52..c93bb3cd 100644 --- a/vnpy/trader/app/spreadTrading/stEngine.py +++ b/vnpy/trader/app/spreadTrading/stEngine.py @@ -6,8 +6,10 @@ from copy import copy from vnpy.event import Event from vnpy.trader.vtFunction import getJsonPath -from vnpy.trader.vtEvent import EVENT_TICK, EVENT_TRADE, EVENT_POSITION -from vnpy.trader.vtObject import VtSubscribeReq, VtLogData +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) @@ -16,8 +18,8 @@ from .stBase import (StLeg, StSpread, EVENT_SPREADTRADING_TICK, ######################################################################## -class StEngine(object): - """""" +class StDataEngine(object): + """价差数据计算引擎""" settingFileName = 'ST_setting.json' settingFilePath = getJsonPath(settingFileName, __file__) @@ -143,15 +145,13 @@ class StEngine(object): spread = self.vtSymbolSpreadDict[tick.vtSymbol] spread.calculatePrice() - # 推送价差行情更新 - newSpread = copy(spread) - + # 推送价差行情更新 event1 = Event(EVENT_SPREADTRADING_TICK+spread.name) - event1.dict_['data'] = newSpread + event1.dict_['data'] = spread self.eventEngine.put(event1) event2 = Event(EVENT_SPREADTRADING_TICK) - event2.dict_['data'] = newSpread + event2.dict_['data'] = spread self.eventEngine.put(event2) #---------------------------------------------------------------------- @@ -184,18 +184,16 @@ class StEngine(object): spread.calculatePos() # 推送价差持仓更新 - newSpread = copy(spread) - event1 = Event(EVENT_SPREADTRADING_POS+spread.name) - event1.dict_['data'] = newSpread + event1.dict_['data'] = spread self.eventEngine.put(event1) event2 = Event(EVENT_SPREADTRADING_POS) - event2.dict_['data'] = newSpread + event2.dict_['data'] = spread self.eventEngine.put(event2) #---------------------------------------------------------------------- - def processPositionEvent(self, event): + def processPosEvent(self, event): """处理持仓推送""" # 检查持仓是否需要处理 pos = event.dict_['data'] @@ -217,14 +215,12 @@ class StEngine(object): spread.calculatePos() # 推送价差持仓更新 - newSpread = copy(spread) - event1 = Event(EVENT_SPREADTRADING_POS+spread.name) - event1.dict_['data'] = newSpread + event1.dict_['data'] = spread self.eventEngine.put(event1) event2 = Event(EVENT_SPREADTRADING_POS) - event2.dict_['data'] = newSpread + event2.dict_['data'] = spread self.eventEngine.put(event2) #---------------------------------------------------------------------- @@ -232,7 +228,7 @@ class StEngine(object): """""" self.eventEngine.register(EVENT_TICK, self.processTickEvent) self.eventEngine.register(EVENT_TRADE, self.processTradeEvent) - self.eventEngine.register(EVENT_POSITION, self.processPositionEvent) + self.eventEngine.register(EVENT_POSITION, self.processPosEvent) #---------------------------------------------------------------------- def subscribeMarketData(self, vtSymbol): @@ -262,4 +258,155 @@ class StEngine(object): """停止""" pass + +######################################################################## +class StAlgoEngine(object): + """价差算法交易引擎""" + + #---------------------------------------------------------------------- + def __init__(self, mainEngine, eventEngine): + """Constructor""" + 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.algoDict.get(trade.vtSymbol, None) + if algo: + algo.updateTrade(trade) + + #---------------------------------------------------------------------- + def processOrderEvent(self, event): + """处理委托事件""" + order = event.dict_['data'] + + algo = self.algoDict.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 = volume + + 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) + return [vtOrderID] + + #---------------------------------------------------------------------- + def sell(self, vtSymbol, price, volume, payup=0): + """卖出""" + vtOrderID = self.sendOrder(vtSymbol, DIRECTION_SHORT, OFFSET_CLOSE, price, volume, payup) + return [vtOrderID] + + #---------------------------------------------------------------------- + def short(self, vtSymbol, price, volume, payup=0): + """卖空""" + vtOrderID = self.sendOrder(vtSymbol, DIRECTION_SHORT, OFFSET_OPEN, price, volume, payup) + return [vtOrderID] + + #---------------------------------------------------------------------- + def cover(self, vtSymbol, price, volume, payup=0): + """平空""" + vtOrderID = self.sendOrder(vtSymbol, DIRECTION_LONG, OFFSET_CLOSE, price, volume, payup) + return [vtOrderID] + + +######################################################################## +class StEngine(object): + """价差引擎""" + + #---------------------------------------------------------------------- + def __init__(self, mainEngine, eventEngine): + """Constructor""" + self.mainEngine = mainEngine + self.eventEngine = eventEngine + + self.dataEngine = StDataEngine(mainEngine, eventEngine) + self.algoEngine = StAlgoEngine(mainEngine, eventEngine) + + #---------------------------------------------------------------------- + def loadSetting(self): + """""" + self.dataEngine.loadSetting() + + + + \ No newline at end of file From 2578e1b41937a06e193a6a7cf2d62b42a2ba1aad Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Wed, 21 Jun 2017 23:12:49 +0800 Subject: [PATCH 08/19] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E4=BB=B7=E5=B7=AE=E7=8B=99=E5=87=BB=E7=AE=97=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vnpy/trader/app/spreadTrading/stAlgo.py | 79 +++++++++++++++++++++---- 1 file changed, 69 insertions(+), 10 deletions(-) diff --git a/vnpy/trader/app/spreadTrading/stAlgo.py b/vnpy/trader/app/spreadTrading/stAlgo.py index ba6e1a86..e3903407 100644 --- a/vnpy/trader/app/spreadTrading/stAlgo.py +++ b/vnpy/trader/app/spreadTrading/stAlgo.py @@ -12,9 +12,12 @@ from vnpy.trader.vtConstant import (EMPTY_INT, EMPTY_FLOAT, ######################################################################## class StAlgoTemplate(object): """价差算法交易模板""" - MODE_LONGSHORT = 'longshort' - MODE_LONGONLY = 'long' - MODE_SHORTONLY = 'short' + MODE_LONGSHORT = 1 + MODE_LONGONLY = 2 + MODE_SHORTONLY = 3 + + SPREAD_LONG = 1 + SPREAD_SHORT = 2 #---------------------------------------------------------------------- def __init__(self, algoEngine, spread): @@ -62,7 +65,7 @@ class StAlgoTemplate(object): raise NotImplementedError #---------------------------------------------------------------------- - def start(self): + def start(self, mode=MODE_LONGSHORT): """""" raise NotImplementedError @@ -84,7 +87,9 @@ class SniperAlgo(StAlgoTemplate): super(SniperAlgo, self).__init__(algoEngine) self.algoName = u'Sniper' - self.hedgeInterval = 2 # 对冲腿撤单再发前的等待时间 + self.quoteInterval = 2 # 主动腿报价撤单再发前等待的时间 + self.quoteCount = 0 # 报价计数 + self.hedgeInterval = 2 # 对冲腿对冲撤单再发前的等待时间 self.hedgeCount = 0 # 对冲计数 self.activeVtSymbol = spread.activeLeg.vtSymbol # 主动腿代码 @@ -103,6 +108,41 @@ class SniperAlgo(StAlgoTemplate): 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) + + # 卖出 + elif (spread.netPos > 0 and + spread.bidPrice >= self.sellPrice): + self.quoteActiveLeg(self.SPREAD_SHORT) + + # 允许做空 + 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) + + # 平空 + elif (spread.netPos < 0 and + spread.askPrice <= self.coverPrice): + self.quoteActiveLeg(self.SPREAD_LONG) #---------------------------------------------------------------------- def updateSpreadPos(self, spread): @@ -144,8 +184,15 @@ class SniperAlgo(StAlgoTemplate): #---------------------------------------------------------------------- def updateTimer(self): """计时更新""" + self.quoteCount += 1 self.hedgeCount += 1 + # 计时到达报价间隔后,则对尚未成交的主动腿委托全部撤单 + # 收到撤单回报后清空委托列表,等待下次价差更新再发单 + if self.quoteCount > self.quoteInterval: + self.cancelLegOrder(self.activeVtSymbol) + self.quoteCount = 0 + # 计时到达对冲间隔后,则对尚未成交的全部被动腿委托全部撤单 # 收到撤单回报后,会自动发送新的对冲委托 if self.hedgeCount > self.hedgeInterval: @@ -153,9 +200,10 @@ class SniperAlgo(StAlgoTemplate): self.hedgeCount = 0 #---------------------------------------------------------------------- - def start(self): + def start(self, mode=MODE_LONGSHORT): """启动""" - raise NotImplementedError + self.mode = mode + self.active = True #---------------------------------------------------------------------- def stop(self): @@ -200,22 +248,31 @@ class SniperAlgo(StAlgoTemplate): """发出主动腿""" spread = self.spread - if direction == DIRECTION_LONG: + 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 leg = self.legDict[self.activeVtSymbol] - legVolume = spreadVolume * leg.ratio - + legVolume = spreadVolume * leg.ratio self.sendLegOrder(leg, legVolume) + + self.quoteCount = 0 # 重置主动腿报价撤单等待计数 #---------------------------------------------------------------------- def hedgePassiveLeg(self, vtSymbol): @@ -234,6 +291,8 @@ class SniperAlgo(StAlgoTemplate): for vtSymbol in self.hedgingTaskDict.keys(): self.hedgePassiveLeg(vtSymbol) + self.hedgeCount = 0 # 重置被动腿对冲撤单等待计数 + #---------------------------------------------------------------------- def newActiveLegTrade(self, trade): """新的主动腿成交""" From 79a9ac2bfb33f81c1c32b351b41fc0bf712787f7 Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Fri, 23 Jun 2017 16:27:05 +0800 Subject: [PATCH 09/19] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E4=BB=B7=E5=B7=AE=E4=BA=A4=E6=98=93=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vnpy/trader/app/spreadTrading/st.ico | Bin 45118 -> 66622 bytes vnpy/trader/app/spreadTrading/stAlgo.py | 122 +++++- vnpy/trader/app/spreadTrading/stBase.py | 2 + vnpy/trader/app/spreadTrading/stEngine.py | 171 ++++++++- vnpy/trader/app/spreadTrading/uiStWidget.py | 405 ++++++++++++++++++-- 5 files changed, 654 insertions(+), 46 deletions(-) diff --git a/vnpy/trader/app/spreadTrading/st.ico b/vnpy/trader/app/spreadTrading/st.ico index a66c245e0dc6c55842f923177515889ca5b9ce4e..4d0ad1991d3d17f3d649562dad09da2e23142166 100644 GIT binary patch literal 66622 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 diff --git a/vnpy/trader/app/spreadTrading/stAlgo.py b/vnpy/trader/app/spreadTrading/stAlgo.py index e3903407..3cb24c70 100644 --- a/vnpy/trader/app/spreadTrading/stAlgo.py +++ b/vnpy/trader/app/spreadTrading/stAlgo.py @@ -12,9 +12,9 @@ from vnpy.trader.vtConstant import (EMPTY_INT, EMPTY_FLOAT, ######################################################################## class StAlgoTemplate(object): """价差算法交易模板""" - MODE_LONGSHORT = 1 - MODE_LONGONLY = 2 - MODE_SHORTONLY = 3 + MODE_LONGSHORT = u'双向' + MODE_LONGONLY = u'做多' + MODE_SHORTONLY = u'做空' SPREAD_LONG = 1 SPREAD_SHORT = 2 @@ -65,7 +65,7 @@ class StAlgoTemplate(object): raise NotImplementedError #---------------------------------------------------------------------- - def start(self, mode=MODE_LONGSHORT): + def start(self): """""" raise NotImplementedError @@ -74,7 +74,79 @@ class StAlgoTemplate(object): """""" 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): + """输出算法日志""" + content = ':'.join([self.spreadName, 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): @@ -82,9 +154,9 @@ class SniperAlgo(StAlgoTemplate): FINISHED_STATUS = [STATUS_ALLTRADED, STATUS_CANCELLED, STATUS_REJECTED] #---------------------------------------------------------------------- - def __init__(self, algoEngine): + def __init__(self, algoEngine, spread): """Constructor""" - super(SniperAlgo, self).__init__(algoEngine) + super(SniperAlgo, self).__init__(algoEngine, spread) self.algoName = u'Sniper' self.quoteInterval = 2 # 主动腿报价撤单再发前等待的时间 @@ -125,11 +197,13 @@ class SniperAlgo(StAlgoTemplate): 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: @@ -138,11 +212,13 @@ class SniperAlgo(StAlgoTemplate): 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): @@ -180,10 +256,14 @@ class SniperAlgo(StAlgoTemplate): # 检查若是被动腿,且已经没有未完成委托,则执行对冲 if not orderList and vtSymbol in self.passiveVtSymbols: self.hedgePassiveLeg(vtSymbol) + self.writeLog(u'发出新的被动腿%s对冲单' %vtSymbol) #---------------------------------------------------------------------- def updateTimer(self): """计时更新""" + if not self.active: + return + self.quoteCount += 1 self.hedgeCount += 1 @@ -200,10 +280,14 @@ class SniperAlgo(StAlgoTemplate): self.hedgeCount = 0 #---------------------------------------------------------------------- - def start(self, mode=MODE_LONGSHORT): + def start(self): """启动""" - self.mode = mode self.active = True + + self.quoteCount = 0 + self.hedgeCount = 0 + + return self.active #---------------------------------------------------------------------- def stop(self): @@ -212,6 +296,8 @@ class SniperAlgo(StAlgoTemplate): self.hedgingTaskDict.clear() self.cancelAllOrders() + + return self.active #---------------------------------------------------------------------- def sendLegOrder(self, leg, legVolume): @@ -310,6 +396,9 @@ class SniperAlgo(StAlgoTemplate): self.hedgingTaskDict[leg.vtSymbol] = newHedgingTask else: self.hedgingTaskDict[leg.vtSymbol] += newHedgingTask + + # 输出日志 + self.writeLog(u'主动腿%s成交,方向%s,数量%s' %(trade.vtSymbol, trade.direction, trade.volume)) #---------------------------------------------------------------------- def newPassiveLegTrade(self, trade): @@ -328,6 +417,9 @@ class SniperAlgo(StAlgoTemplate): if not self.hedgingTaskDict[trade.vtSymbol]: del self.hedgingTaskDict[trade.vtSymbol] + # 输出日志 + self.writeLog(u'被动腿%s成交,方向%s,数量%s' %(trade.vtSymbol, trade.direction, trade.volume)) + #---------------------------------------------------------------------- def cancelLegOrder(self, vtSymbol): """撤销某条腿的委托""" @@ -339,15 +431,27 @@ class SniperAlgo(StAlgoTemplate): 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: - self.cancelLegOrder(vtSymbol) \ No newline at end of file + if 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 index 64f0f00f..71f25f15 100644 --- a/vnpy/trader/app/spreadTrading/stBase.py +++ b/vnpy/trader/app/spreadTrading/stBase.py @@ -13,6 +13,8 @@ from vnpy.trader.vtConstant import (EMPTY_INT, EMPTY_FLOAT, EVENT_SPREADTRADING_TICK = 'eSpreadTradingTick.' EVENT_SPREADTRADING_POS = 'eSpreadTradingPos.' EVENT_SPREADTRADING_LOG = 'eSpreadTradingLog' +EVENT_SPREADTRADING_ALGO = 'eSpreadTradingAlgo.' +EVENT_SPREADTRADING_ALGOLOG = 'eSpreadTradingAlgoLog' diff --git a/vnpy/trader/app/spreadTrading/stEngine.py b/vnpy/trader/app/spreadTrading/stEngine.py index c93bb3cd..71e89c31 100644 --- a/vnpy/trader/app/spreadTrading/stEngine.py +++ b/vnpy/trader/app/spreadTrading/stEngine.py @@ -2,10 +2,10 @@ import json import traceback -from copy import copy +import shelve from vnpy.event import Event -from vnpy.trader.vtFunction import getJsonPath +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, @@ -14,7 +14,9 @@ from vnpy.trader.vtConstant import (DIRECTION_LONG, DIRECTION_SHORT, OFFSET_OPEN, OFFSET_CLOSE) from .stBase import (StLeg, StSpread, EVENT_SPREADTRADING_TICK, - EVENT_SPREADTRADING_POS, EVENT_SPREADTRADING_LOG) + EVENT_SPREADTRADING_POS, EVENT_SPREADTRADING_LOG, + EVENT_SPREADTRADING_ALGO, EVENT_SPREADTRADING_ALGOLOG) +from .stAlgo import SniperAlgo ######################################################################## @@ -121,6 +123,9 @@ class StDataEngine(object): # 初始化价差 spread.initSpread() + self.putSpreadTickEvent(spread) + self.putSpreadPosEvent(spread) + # 返回结果 result = True msg = u'%s价差创建成功' %spread.name @@ -145,7 +150,12 @@ class StDataEngine(object): 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) @@ -215,13 +225,19 @@ class StDataEngine(object): 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) + self.eventEngine.put(event2) + #---------------------------------------------------------------------- def registerEvent(self): @@ -236,6 +252,7 @@ class StDataEngine(object): contract = self.mainEngine.getContract(vtSymbol) if not contract: self.writeLog(u'订阅行情失败,找不到该合约%s' %vtSymbol) + return req = VtSubscribeReq() req.symbol = contract.symbol @@ -254,18 +271,21 @@ class StDataEngine(object): self.eventEngine.put(event) #---------------------------------------------------------------------- - def stop(self): - """停止""" - pass + def getAllSpreads(self): + """获取所有的价差""" + return self.spreadDict.values() ######################################################################## class StAlgoEngine(object): """价差算法交易引擎""" + algoFileName = 'SpreadTradingAlgo.vt' + algoFilePath = getTempPath(algoFileName) #---------------------------------------------------------------------- - def __init__(self, mainEngine, eventEngine): + def __init__(self, dataEngine, mainEngine, eventEngine): """Constructor""" + self.dataEngine = dataEngine self.mainEngine = mainEngine self.eventEngine = eventEngine @@ -387,6 +407,122 @@ class StAlgoEngine(object): vtOrderID = self.sendOrder(vtSymbol, DIRECTION_LONG, OFFSET_CLOSE, price, volume, payup) return [vtOrderID] + #---------------------------------------------------------------------- + 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: + self.algoDict[spread.name] = SniperAlgo(self, spread) + + # 加载配置 + 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): @@ -399,12 +535,21 @@ class StEngine(object): self.eventEngine = eventEngine self.dataEngine = StDataEngine(mainEngine, eventEngine) - self.algoEngine = StAlgoEngine(mainEngine, eventEngine) + self.algoEngine = StAlgoEngine(self.dataEngine, mainEngine, eventEngine) #---------------------------------------------------------------------- - def loadSetting(self): - """""" + def init(self): + """初始化""" self.dataEngine.loadSetting() + self.algoEngine.loadSetting() + + #---------------------------------------------------------------------- + def stop(self): + """停止""" + self.dataEngine.saveSetting() + + self.algoEngine.stopAll() + self.algoEngine.saveSetting() diff --git a/vnpy/trader/app/spreadTrading/uiStWidget.py b/vnpy/trader/app/spreadTrading/uiStWidget.py index 9244fc4d..76fd714e 100644 --- a/vnpy/trader/app/spreadTrading/uiStWidget.py +++ b/vnpy/trader/app/spreadTrading/uiStWidget.py @@ -2,14 +2,20 @@ from collections import OrderedDict -from vnpy.trader.uiQt import QtWidgets +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_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): @@ -64,25 +70,365 @@ class StPosMonitor(BasicMonitor): ######################################################################## -class StLogMonitor(BasicMonitor): +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(StLogMonitor, self).__init__(mainEngine, eventEngine, parent) + super(StAlgoLogMonitor, self).__init__(mainEngine, eventEngine, parent) d = OrderedDict() d['logTime'] = {'chinese':u'时间', 'cellType':BasicCell} - d['logContent'] = {'chinese':u'日志', 'cellType':BasicCell} + d['logContent'] = {'chinese':u'信息', 'cellType':BasicCell} self.setHeaderDict(d) - self.setEventType(EVENT_SPREADTRADING_LOG) + self.setEventType(EVENT_SPREADTRADING_ALGOLOG) self.setFont(BASIC_FONT) self.initTable() - self.registerEvent() + 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: + algoActive = self.algoEngine.stopAlgo(self.spreadName) + + if not algoActive: + self.setStopped() + else: + 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.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) + + +######################################################################## +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): @@ -105,37 +451,48 @@ class StManager(QtWidgets.QWidget): self.setWindowTitle(u'价差交易') # 创建按钮 - buttonLoadSetting = QtWidgets.QPushButton(u'加载配置') - - buttonLoadSetting.clicked.connect(self.stEngine.loadSetting) + buttonInit = QtWidgets.QPushButton(u'初始化') + buttonInit.clicked.connect(self.init) # 创建组件 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) + + # 创建集合 + groupTick = StGroup(tickMonitor, u'价差行情') + groupPos = StGroup(posMonitor, u'价差持仓') + groupLog = StGroup(logMonitor, u'日志信息') + groupAlgo = StGroup(self.algoManager, u'价差算法') + groupAlgoLog = StGroup(algoLogMonitor, u'算法信息') # 设置布局 - hbox1 = QtWidgets.QHBoxLayout() - hbox1.addWidget(buttonLoadSetting) - hbox1.addStretch() + hbox = QtWidgets.QHBoxLayout() + hbox.addWidget(buttonInit) + hbox.addStretch() - hbox2 = QtWidgets.QHBoxLayout() - hbox2.addWidget(tickMonitor) - hbox2.addWidget(posMonitor) - - vbox = QtWidgets.QVBoxLayout() - vbox.addLayout(hbox1) - vbox.addLayout(hbox2) - vbox.addWidget(logMonitor) - - self.setLayout(vbox) + 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() From 1aea18504a8da9826ef2306ead6f9c78fe14fbbc Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Tue, 27 Jun 2017 13:39:21 +0800 Subject: [PATCH 10/19] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=B8=8A=E6=B5=B7?= =?UTF-8?q?=E4=B8=AD=E6=9C=9F=E5=8E=86=E5=8F=B2=E6=95=B0=E6=8D=AE=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vnpy/api/shcifco/__init__.py | 0 vnpy/api/shcifco/vnshcifco.py | 138 ++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 vnpy/api/shcifco/__init__.py create mode 100644 vnpy/api/shcifco/vnshcifco.py diff --git a/vnpy/api/shcifco/__init__.py b/vnpy/api/shcifco/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/vnpy/api/shcifco/vnshcifco.py b/vnpy/api/shcifco/vnshcifco.py new file mode 100644 index 00000000..06626e97 --- /dev/null +++ b/vnpy/api/shcifco/vnshcifco.py @@ -0,0 +1,138 @@ +# encoding: UTF-8 + + +import requests + +HTTP_OK = 200 + + +######################################################################## +class DataApi(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) + + 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) + + price = float(data) + return price + + #---------------------------------------------------------------------- + def getLastBar(self, symbol): + """获取最新的一分钟K线数据""" + path = 'lastbar' + params = {'ids': symbol} + data = self.getData(path, params) + + 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([6]) + } + return d + + #---------------------------------------------------------------------- + def getHisBar(self, symbol, num, date='', period=''): + """获取历史K线数据""" + path = 'lastbar' + + # 默认参数 + params = { + 'ids': symbol, + 'num': num + } + # 可选参数 + if date: + params[date] = date + if period: + params[period] = period + + data = self.getData(path, params) + + barList = [] + l = data.split(';') + + for barStr in l: + barData = barStr.split(',') + d = { + 'symbol': barData[0], + 'time': barData[1], + 'open': float(barData[2]), + 'high': float(barData[3]), + 'low': float(barData[4]), + 'close': float(barData[5]), + 'volume': int([6]) + } + barList.append(d) + + return barList + + +if __name__ == "__main__": + ip = '101.231.179.199' + port = '10102' + token = 'testd2cda34b2d317779e812eb84ee4224a6_123456' + + api = DataApi(ip, port, token) + api.getData(path, params) + + print api.getLastTick('cu1709') + + print api.getLastPrice('cu1709') + + print api.getLastBar('cu1709') + + print api.getHisBar('cu1709', 50) + + \ No newline at end of file From b3cdb0e2c0060a71a43fa5fe68535559093b359f Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Tue, 27 Jun 2017 16:55:37 +0800 Subject: [PATCH 11/19] =?UTF-8?q?=E5=A2=9E=E5=8A=A0vnpy.data=E7=94=A8?= =?UTF-8?q?=E4=BA=8E=E5=AD=98=E6=94=BE=E6=95=B0=E6=8D=AE=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E7=9A=84=E5=8A=9F=E8=83=BD=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- {vnpy/api => archive}/datayes/README.md | 0 {vnpy/api => archive}/datayes/__init__.py | 0 {vnpy/api => archive}/datayes/api.py | 0 .../datayes/config/db_EQU_D1.csv | 0 {vnpy/api => archive}/datayes/download.sh | 0 {vnpy/api => archive}/datayes/errors.py | 0 {vnpy/api => archive}/datayes/fun/fetch.R | 0 .../datayes/names/equTicker.json | 0 .../datayes/names/fudTicker.json | 0 .../datayes/names/futTicker.json | 0 .../datayes/names/idxTicker.json | 0 .../datayes/names/optTicker.json | 0 .../api => archive}/datayes/names/secID.json | 0 {vnpy/api => archive}/datayes/prepare.sh | 0 .../datayes/static/figs/fig1.png | Bin .../datayes/static/figs/fig2.png | Bin .../datayes/static/figs/fig3.png | Bin .../datayes/static/figs/fig4.png | Bin .../datayes/static/figs/fig5.png | Bin .../datayes/static/figs/fig6.png | Bin .../datayes/static/tutorial.ipynb | 0 .../datayes/static/tutorial.md | 0 {vnpy/api => archive}/datayes/storage.py | 0 {vnpy/api => archive}/datayes/tests.py | 0 {vnpy/api => archive}/datayes/update.sh | 0 vnpy/data/README.md | 5 ++ vnpy/{api/shcifco => data}/__init__.py | 0 vnpy/data/datayes/__init__.py | 1 + vnpy/data/datayes/vndatayes.py | 52 ++++++++++++++++++ vnpy/data/shcifco/__init__.py | 0 vnpy/{api => data}/shcifco/vnshcifco.py | 4 +- vnpy/trader/app/ctaStrategy/ctaHistoryData.py | 6 +- vnpy/trader/app/spreadTrading/stEngine.py | 4 +- 33 files changed, 66 insertions(+), 6 deletions(-) rename {vnpy/api => archive}/datayes/README.md (100%) rename {vnpy/api => archive}/datayes/__init__.py (100%) rename {vnpy/api => archive}/datayes/api.py (100%) rename {vnpy/api => archive}/datayes/config/db_EQU_D1.csv (100%) rename {vnpy/api => archive}/datayes/download.sh (100%) rename {vnpy/api => archive}/datayes/errors.py (100%) rename {vnpy/api => archive}/datayes/fun/fetch.R (100%) rename {vnpy/api => archive}/datayes/names/equTicker.json (100%) rename {vnpy/api => archive}/datayes/names/fudTicker.json (100%) rename {vnpy/api => archive}/datayes/names/futTicker.json (100%) rename {vnpy/api => archive}/datayes/names/idxTicker.json (100%) rename {vnpy/api => archive}/datayes/names/optTicker.json (100%) rename {vnpy/api => archive}/datayes/names/secID.json (100%) rename {vnpy/api => archive}/datayes/prepare.sh (100%) rename {vnpy/api => archive}/datayes/static/figs/fig1.png (100%) rename {vnpy/api => archive}/datayes/static/figs/fig2.png (100%) rename {vnpy/api => archive}/datayes/static/figs/fig3.png (100%) rename {vnpy/api => archive}/datayes/static/figs/fig4.png (100%) rename {vnpy/api => archive}/datayes/static/figs/fig5.png (100%) rename {vnpy/api => archive}/datayes/static/figs/fig6.png (100%) rename {vnpy/api => archive}/datayes/static/tutorial.ipynb (100%) rename {vnpy/api => archive}/datayes/static/tutorial.md (100%) rename {vnpy/api => archive}/datayes/storage.py (100%) rename {vnpy/api => archive}/datayes/tests.py (100%) rename {vnpy/api => archive}/datayes/update.sh (100%) create mode 100644 vnpy/data/README.md rename vnpy/{api/shcifco => data}/__init__.py (100%) create mode 100644 vnpy/data/datayes/__init__.py create mode 100644 vnpy/data/datayes/vndatayes.py create mode 100644 vnpy/data/shcifco/__init__.py rename vnpy/{api => data}/shcifco/vnshcifco.py (98%) 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/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/api/shcifco/__init__.py b/vnpy/data/__init__.py similarity index 100% rename from vnpy/api/shcifco/__init__.py rename to vnpy/data/__init__.py 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/api/shcifco/vnshcifco.py b/vnpy/data/shcifco/vnshcifco.py similarity index 98% rename from vnpy/api/shcifco/vnshcifco.py rename to vnpy/data/shcifco/vnshcifco.py index 06626e97..94927d9c 100644 --- a/vnpy/api/shcifco/vnshcifco.py +++ b/vnpy/data/shcifco/vnshcifco.py @@ -7,7 +7,7 @@ HTTP_OK = 200 ######################################################################## -class DataApi(object): +class ShcifcoApi(object): """数据接口""" #---------------------------------------------------------------------- @@ -124,7 +124,7 @@ if __name__ == "__main__": port = '10102' token = 'testd2cda34b2d317779e812eb84ee4224a6_123456' - api = DataApi(ip, port, token) + api = ShcifcoApi(ip, port, token) api.getData(path, params) print api.getLastTick('cu1709') diff --git a/vnpy/trader/app/ctaStrategy/ctaHistoryData.py b/vnpy/trader/app/ctaStrategy/ctaHistoryData.py index 5366468d..d9c97375 100644 --- a/vnpy/trader/app/ctaStrategy/ctaHistoryData.py +++ b/vnpy/trader/app/ctaStrategy/ctaHistoryData.py @@ -13,10 +13,10 @@ 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 # 以下为vn.trader和通联数据规定的交易所代码映射 @@ -33,10 +33,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/spreadTrading/stEngine.py b/vnpy/trader/app/spreadTrading/stEngine.py index 71e89c31..6789da12 100644 --- a/vnpy/trader/app/spreadTrading/stEngine.py +++ b/vnpy/trader/app/spreadTrading/stEngine.py @@ -11,7 +11,8 @@ from vnpy.trader.vtEvent import (EVENT_TICK, EVENT_TRADE, EVENT_POSITION, from vnpy.trader.vtObject import (VtSubscribeReq, VtOrderReq, VtCancelOrderReq, VtLogData) from vnpy.trader.vtConstant import (DIRECTION_LONG, DIRECTION_SHORT, - OFFSET_OPEN, OFFSET_CLOSE) + OFFSET_OPEN, OFFSET_CLOSE, + PRICETYPE_LIMITPRICE) from .stBase import (StLeg, StSpread, EVENT_SPREADTRADING_TICK, EVENT_SPREADTRADING_POS, EVENT_SPREADTRADING_LOG, @@ -358,6 +359,7 @@ class StAlgoEngine(object): req.direction = direction req.offset = offset req.volume = volume + req.priceType = PRICETYPE_LIMITPRICE if direction == DIRECTION_LONG: req.price = price + payup * contract.priceTick From 788aa4acb7795387d4e309c6b4f0434ceb4fa5a7 Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Tue, 27 Jun 2017 17:14:35 +0800 Subject: [PATCH 12/19] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BB=B7=E5=B7=AE?= =?UTF-8?q?=E4=BA=A4=E6=98=93=E6=A8=A1=E5=9D=97=E7=9A=84=E4=B8=80=E9=94=AE?= =?UTF-8?q?=E5=85=A8=E5=81=9C=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vnpy/trader/app/spreadTrading/stAlgo.py | 18 +++---- vnpy/trader/app/spreadTrading/uiStWidget.py | 49 ++++++++++++++----- .../gateway/ctpGateway/CTP_connect.json | 4 +- 3 files changed, 47 insertions(+), 24 deletions(-) diff --git a/vnpy/trader/app/spreadTrading/stAlgo.py b/vnpy/trader/app/spreadTrading/stAlgo.py index 3cb24c70..afd4f743 100644 --- a/vnpy/trader/app/spreadTrading/stAlgo.py +++ b/vnpy/trader/app/spreadTrading/stAlgo.py @@ -282,21 +282,21 @@ class SniperAlgo(StAlgoTemplate): #---------------------------------------------------------------------- def start(self): """启动""" + if not self.active: + self.quoteCount = 0 + self.hedgeCount = 0 + self.active = True - - self.quoteCount = 0 - self.hedgeCount = 0 - return self.active #---------------------------------------------------------------------- def stop(self): """停止""" - self.active = False - - self.hedgingTaskDict.clear() - self.cancelAllOrders() - + if self.active: + self.hedgingTaskDict.clear() + self.cancelAllOrders() + + self.active = False return self.active #---------------------------------------------------------------------- diff --git a/vnpy/trader/app/spreadTrading/uiStWidget.py b/vnpy/trader/app/spreadTrading/uiStWidget.py index 76fd714e..d2613146 100644 --- a/vnpy/trader/app/spreadTrading/uiStWidget.py +++ b/vnpy/trader/app/spreadTrading/uiStWidget.py @@ -319,16 +319,25 @@ class StActiveButton(QtWidgets.QPushButton): def buttonClicked(self): """改变运行模式""" if self.active: - algoActive = self.algoEngine.stopAlgo(self.spreadName) - - if not algoActive: - self.setStopped() + self.stop else: - algoActive = self.algoEngine.startAlgo(self.spreadName) - - if algoActive: - self.setStarted() + 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): """算法启动""" @@ -350,7 +359,7 @@ class StActiveButton(QtWidgets.QPushButton): ######################################################################## class StAlgoManager(QtWidgets.QTableWidget): - """""" + """价差算法管理组件""" #---------------------------------------------------------------------- def __init__(self, stEngine, parent=None): @@ -359,6 +368,8 @@ class StAlgoManager(QtWidgets.QTableWidget): self.algoEngine = stEngine.algoEngine + self.buttonActiveDict = {} # spreadName: buttonActive + self.initUi() #---------------------------------------------------------------------- @@ -413,6 +424,14 @@ class StAlgoManager(QtWidgets.QTableWidget): 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() ######################################################################## @@ -450,10 +469,6 @@ class StManager(QtWidgets.QWidget): """初始化界面""" self.setWindowTitle(u'价差交易') - # 创建按钮 - buttonInit = QtWidgets.QPushButton(u'初始化') - buttonInit.clicked.connect(self.init) - # 创建组件 tickMonitor = StTickMonitor(self.mainEngine, self.eventEngine) posMonitor = StPosMonitor(self.mainEngine, self.eventEngine) @@ -461,6 +476,13 @@ class StManager(QtWidgets.QWidget): 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'价差持仓') @@ -472,6 +494,7 @@ class StManager(QtWidgets.QWidget): hbox = QtWidgets.QHBoxLayout() hbox.addWidget(buttonInit) hbox.addStretch() + hbox.addWidget(buttonStopAll) grid = QtWidgets.QGridLayout() grid.addLayout(hbox, 0, 0, 1, 2) diff --git a/vnpy/trader/gateway/ctpGateway/CTP_connect.json b/vnpy/trader/gateway/ctpGateway/CTP_connect.json index e4bfa6b8..1b4d5a42 100644 --- a/vnpy/trader/gateway/ctpGateway/CTP_connect.json +++ b/vnpy/trader/gateway/ctpGateway/CTP_connect.json @@ -2,6 +2,6 @@ "brokerID": "9999", "mdAddress": "tcp://180.168.146.187:10011", "tdAddress": "tcp://180.168.146.187:10001", - "userID": "simnow申请", - "password": "simnow申请" + "userID": "000300", + "password": "19890624" } \ No newline at end of file From 654c7d0e8a4f7faa3dc805d11643588a27464ca9 Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Tue, 27 Jun 2017 17:29:22 +0800 Subject: [PATCH 13/19] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=8F=AF=E8=83=BD?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E6=9F=90=E4=BA=9B=E6=9C=9F=E6=9D=83=E5=92=8C?= =?UTF-8?q?=E4=BB=B7=E5=B7=AE=E8=A1=8C=E6=83=85Tick=E4=B8=A2=E5=A4=B1?= =?UTF-8?q?=E7=9A=84=E8=BF=87=E6=BB=A4=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vnpy/trader/gateway/ctpGateway/ctpGateway.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/vnpy/trader/gateway/ctpGateway/ctpGateway.py b/vnpy/trader/gateway/ctpGateway/ctpGateway.py index 2294cd21..4354080b 100644 --- a/vnpy/trader/gateway/ctpGateway/ctpGateway.py +++ b/vnpy/trader/gateway/ctpGateway/ctpGateway.py @@ -329,10 +329,6 @@ class CtpMdApi(MdApi): #---------------------------------------------------------------------- def onRtnDepthMarketData(self, data): """行情推送""" - # 忽略成交量为0的无效tick数据 - if not data['Volume']: - return - # 创建对象 tick = VtTickData() tick.gatewayName = self.gatewayName @@ -710,7 +706,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) From ed7e7a772818ab4c6a666e6f5abfb8b4927e5cab Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Tue, 27 Jun 2017 18:00:07 +0800 Subject: [PATCH 14/19] =?UTF-8?q?=E5=A2=9E=E5=8A=A0DataRecording=E8=A1=8C?= =?UTF-8?q?=E6=83=85=E8=AE=B0=E5=BD=95=E4=BD=BF=E7=94=A8=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/DataRecording/runDataRecording.py | 88 +++++++++++++++++++ .../gateway/ctpGateway/CTP_connect.json | 4 +- 2 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 examples/DataRecording/runDataRecording.py diff --git a/examples/DataRecording/runDataRecording.py b/examples/DataRecording/runDataRecording.py new file mode 100644 index 00000000..73c77f6f --- /dev/null +++ b/examples/DataRecording/runDataRecording.py @@ -0,0 +1,88 @@ +# 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 + + +#---------------------------------------------------------------------- +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'] + content = '%s:%s' %(log.gatewayName, log.logContent) + printLog(content) + +#---------------------------------------------------------------------- +def runChildProcess(): + """子进程运行函数""" + print '-'*20 + printLog(u'启动行情记录运行子进程') + + ee = EventEngine2() + printLog(u'事件引擎创建成功') + + me = MainEngine(ee) + 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, 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__': + runParentProcess() \ No newline at end of file diff --git a/vnpy/trader/gateway/ctpGateway/CTP_connect.json b/vnpy/trader/gateway/ctpGateway/CTP_connect.json index 1b4d5a42..e4bfa6b8 100644 --- a/vnpy/trader/gateway/ctpGateway/CTP_connect.json +++ b/vnpy/trader/gateway/ctpGateway/CTP_connect.json @@ -2,6 +2,6 @@ "brokerID": "9999", "mdAddress": "tcp://180.168.146.187:10011", "tdAddress": "tcp://180.168.146.187:10001", - "userID": "000300", - "password": "19890624" + "userID": "simnow申请", + "password": "simnow申请" } \ No newline at end of file From f58f2be4ee16efca688a96b10de5db16f275f3be Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Tue, 27 Jun 2017 21:52:36 +0800 Subject: [PATCH 15/19] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=A1=8C=E6=83=85?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E5=B7=A5=E5=85=B7=EF=BC=8C=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=97=A0=E5=9B=BE=E5=BD=A2=E7=95=8C=E9=9D=A2CTA=E7=AD=96?= =?UTF-8?q?=E7=95=A5=E8=BF=90=E8=A1=8C=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/CtaTrading/runCtaTrading.py | 104 +++++++++++++++++++++ examples/DataRecording/runDataRecording.py | 5 +- examples/README.md | 11 +++ vnpy/trader/app/ctaStrategy/ctaEngine.py | 20 +++- vnpy/trader/app/ctaStrategy/uiCtaWidget.py | 9 +- 5 files changed, 141 insertions(+), 8 deletions(-) create mode 100644 examples/CtaTrading/runCtaTrading.py create mode 100644 examples/README.md diff --git a/examples/CtaTrading/runCtaTrading.py b/examples/CtaTrading/runCtaTrading.py new file mode 100644 index 00000000..70fa4c22 --- /dev/null +++ b/examples/CtaTrading/runCtaTrading.py @@ -0,0 +1,104 @@ +# 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'] + content = '%s:%s' %(log.gatewayName, 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, processLogEvent) + printLog(u'注册日志事件监听') + + me.connect('CTP') + printLog(u'连接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__': + runParentProcess() \ No newline at end of file diff --git a/examples/DataRecording/runDataRecording.py b/examples/DataRecording/runDataRecording.py index 73c77f6f..dc6846b8 100644 --- a/examples/DataRecording/runDataRecording.py +++ b/examples/DataRecording/runDataRecording.py @@ -7,7 +7,8 @@ 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): @@ -32,6 +33,8 @@ def runChildProcess(): printLog(u'事件引擎创建成功') me = MainEngine(ee) + me.addGateway(ctpGateway) + me.addApp(dataRecorder) printLog(u'主引擎创建成功') ee.register(EVENT_LOG, processLogEvent) diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..148cecf1 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,11 @@ +# vn.py项目的应用示例 + +本文件夹中的内容主要是关于如何在交易业务中使用vn.py的示例 + +* VnTrader:最常用的vn.py图形交易界面 + +* DataRecording:全自动行情记录工具(无需用户每日定时重启) + +* CtaTrading:纯命令行模式的CTA策略交易(尽管同样实现了无人值守,但强烈建议每天启动时人工检查,为自己的PNL负责) + +* CtaBacktesting:CTA策略的回测和优化 \ No newline at end of file diff --git a/vnpy/trader/app/ctaStrategy/ctaEngine.py b/vnpy/trader/app/ctaStrategy/ctaEngine.py index 2a2178b5..319209eb 100644 --- a/vnpy/trader/app/ctaStrategy/ctaEngine.py +++ b/vnpy/trader/app/ctaStrategy/ctaEngine.py @@ -463,7 +463,25 @@ 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): diff --git a/vnpy/trader/app/ctaStrategy/uiCtaWidget.py b/vnpy/trader/app/ctaStrategy/uiCtaWidget.py index 62366a89..7223a673 100644 --- a/vnpy/trader/app/ctaStrategy/uiCtaWidget.py +++ b/vnpy/trader/app/ctaStrategy/uiCtaWidget.py @@ -228,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): From bed06834e3d1f65be3bfba577c5d0ac3cd371c3f Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Wed, 28 Jun 2017 15:36:24 +0800 Subject: [PATCH 16/19] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=97=A0=E7=95=8C?= =?UTF-8?q?=E9=9D=A2CTA=E7=AD=96=E7=95=A5=E4=BA=A4=E6=98=93=E7=9A=84Exampl?= =?UTF-8?q?e=EF=BC=8C=E4=BF=AE=E6=94=B9DataRecorder=E7=9A=84=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=96=87=E4=BB=B6=E4=B8=BAcsv=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/CtaTrading/runCtaTrading.py | 21 +++- examples/DataRecording/runDataRecording.py | 8 +- examples/README.md | 2 +- vnpy/trader/app/dataRecorder/DR_setting.csv | 7 ++ vnpy/trader/app/dataRecorder/DR_setting.json | 32 ------ vnpy/trader/app/dataRecorder/drEngine.py | 105 +++++++------------ 6 files changed, 72 insertions(+), 103 deletions(-) create mode 100644 vnpy/trader/app/dataRecorder/DR_setting.csv delete mode 100644 vnpy/trader/app/dataRecorder/DR_setting.json diff --git a/examples/CtaTrading/runCtaTrading.py b/examples/CtaTrading/runCtaTrading.py index 70fa4c22..bd5eb84b 100644 --- a/examples/CtaTrading/runCtaTrading.py +++ b/examples/CtaTrading/runCtaTrading.py @@ -21,7 +21,17 @@ def printLog(content): def processLogEvent(event): """处理日志事件""" log = event.dict_['data'] - content = '%s:%s' %(log.gatewayName, log.logContent) + 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) #---------------------------------------------------------------------- @@ -39,12 +49,14 @@ def runChildProcess(): printLog(u'主引擎创建成功') ee.register(EVENT_LOG, processLogEvent) - ee.register(EVENT_CTA_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() @@ -101,4 +113,7 @@ def runParentProcess(): if __name__ == '__main__': - runParentProcess() \ No newline at end of file + runChildProcess() + + # 尽管同样实现了无人值守,但强烈建议每天启动时人工检查,为自己的PNL负责 + # runParentProcess() \ No newline at end of file diff --git a/examples/DataRecording/runDataRecording.py b/examples/DataRecording/runDataRecording.py index dc6846b8..b4997c14 100644 --- a/examples/DataRecording/runDataRecording.py +++ b/examples/DataRecording/runDataRecording.py @@ -20,7 +20,10 @@ def printLog(content): def processLogEvent(event): """处理日志事件""" log = event.dict_['data'] - content = '%s:%s' %(log.gatewayName, log.logContent) + if log.gatewayName: + content = '%s:%s' %(log.gatewayName, log.logContent) + else: + content = '%s:%s' %('MainEngine', log.logContent) printLog(content) #---------------------------------------------------------------------- @@ -88,4 +91,5 @@ def runParentProcess(): if __name__ == '__main__': - runParentProcess() \ No newline at end of file + runChildProcess() + #runParentProcess() \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index 148cecf1..c95316f3 100644 --- a/examples/README.md +++ b/examples/README.md @@ -6,6 +6,6 @@ * DataRecording:全自动行情记录工具(无需用户每日定时重启) -* CtaTrading:纯命令行模式的CTA策略交易(尽管同样实现了无人值守,但强烈建议每天启动时人工检查,为自己的PNL负责) +* CtaTrading:无图形界面模式的CTA策略交易 * CtaBacktesting:CTA策略的回测和优化 \ No newline at end of file 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 deleted file mode 100644 index f7c297c5..00000000 --- a/vnpy/trader/app/dataRecorder/DR_setting.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "working": false, - - "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"] - ], - - "active": - { - "IF0000": "IF1605", - "IH0000": "IH1605", - "IC0000": "IC1605" - } -} \ No newline at end of file diff --git a/vnpy/trader/app/dataRecorder/drEngine.py b/vnpy/trader/app/dataRecorder/drEngine.py index 56d8eba2..431c41c3 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 @@ -27,7 +28,7 @@ from vnpy.trader.app.dataRecorder.language import text class DrEngine(object): """数据记录引擎""" - settingFileName = 'DR_setting.json' + settingFileName = 'DR_setting.csv' path = os.path.abspath(os.path.dirname(__file__)) settingFileName = os.path.join(path, settingFileName) @@ -57,84 +58,58 @@ class DrEngine(object): # 载入设置,订阅行情 self.loadSetting() + # 启动数据插入线程 + self.start() + + # 注册事件监听 + self.registerEvent() + #---------------------------------------------------------------------- def loadSetting(self): - """载入设置""" + """加载配置""" with open(self.settingFileName) as f: - drSetting = json.load(f) + drSetting = csv.DictReader(f) - # 如果working设为False则不启动行情记录功能 - working = drSetting['working'] - if not working: - return - - if 'tick' in drSetting: - l = drSetting['tick'] + for d in drSetting: + # 读取配置 + gatewayName = d['gateway'] + symbol = d['symbol'] + exchange = d['symbol'] + currency = d['currency'] + productClass = d['product'] + recordTick = d['tick'] + recordBar = d['bar'] + activeSymbol = d['active'] - for setting in l: - symbol = setting[0] + if exchange: + vtSymbol = '.'.join([symbol, exchange]) + else: 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 - - if len(setting)>=3: - req.exchange = setting[2] - vtSymbol = '.'.join([symbol, req.exchange]) - - 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'] + # 订阅行情 + req = VtSubscribeReq() + req.symbol = symbol + req.exchange = exchange + req.currency = currency + req.productClass = productClass + self.mainEngine.subscribe(req, gatewayName) - # 注意这里的vtSymbol对于IB和LTS接口,应该后缀.交易所 - for activeSymbol, vtSymbol in d.items(): + # 设置需要记录的数据 + if recordTick: + self.tickDict[vtSymbol] = VtTickData() + + if recordBar: + self.barDict[vtSymbol] = VtBarData() + + if activeSymbol: self.activeSymbolDict[vtSymbol] = activeSymbol - - # 启动数据插入线程 - self.start() - - # 注册事件监听 - self.registerEvent() #---------------------------------------------------------------------- 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') From 676ce4a649b2a8be7745e873262d8932c832cb06 Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Wed, 28 Jun 2017 17:40:57 +0800 Subject: [PATCH 17/19] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=A1=8C=E6=83=85?= =?UTF-8?q?=E8=AE=B0=E5=BD=95UI=E7=9A=84=E7=95=8C=E9=9D=A2=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96Bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vnpy/trader/app/dataRecorder/drEngine.py | 12 +++++ vnpy/trader/app/dataRecorder/uiDrWidget.py | 55 +++++++++------------- 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/vnpy/trader/app/dataRecorder/drEngine.py b/vnpy/trader/app/dataRecorder/drEngine.py index 431c41c3..31f1b5a3 100644 --- a/vnpy/trader/app/dataRecorder/drEngine.py +++ b/vnpy/trader/app/dataRecorder/drEngine.py @@ -50,6 +50,9 @@ class DrEngine(object): # K线对象字典 self.barDict = {} + # 配置字典 + self.settingDict = OrderedDict() + # 负责执行数据库插入的单独线程相关 self.active = False # 工作状态 self.queue = Queue() # 队列 @@ -96,6 +99,7 @@ class DrEngine(object): # 设置需要记录的数据 if recordTick: + tick = VtTickData() self.tickDict[vtSymbol] = VtTickData() if recordBar: @@ -103,6 +107,14 @@ class DrEngine(object): if activeSymbol: self.activeSymbolDict[vtSymbol] = activeSymbol + + # 保存配置到缓存中 + self.settingDict[vtSymbol] = d + + #---------------------------------------------------------------------- + def getSetting(self): + """获取配置""" + return self.settingDict #---------------------------------------------------------------------- def procecssTickEvent(self, event): 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() From 9eee8ea166c62443897be61cbb54bb07053a73a5 Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Fri, 30 Jun 2017 11:22:53 +0800 Subject: [PATCH 18/19] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=B8=8A=E6=B5=B7?= =?UTF-8?q?=E4=B8=AD=E6=9C=9F=E5=8E=86=E5=8F=B2=E8=A1=8C=E6=83=85=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3vn.shcifco?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/VnTrader/run.py | 4 +- vnpy/data/shcifco/test.py | 27 +++++++ vnpy/data/shcifco/vnshcifco.py | 70 +++++++++++-------- vnpy/trader/app/dataRecorder/drEngine.py | 2 +- vnpy/trader/app/spreadTrading/ST_setting.json | 21 ++++++ vnpy/trader/app/spreadTrading/stAlgo.py | 11 ++- vnpy/trader/app/spreadTrading/stEngine.py | 35 ++++++---- vnpy/trader/app/spreadTrading/uiStWidget.py | 3 +- vnpy/trader/uiMainWindow.py | 32 +++++---- 9 files changed, 139 insertions(+), 66 deletions(-) create mode 100644 vnpy/data/shcifco/test.py diff --git a/examples/VnTrader/run.py b/examples/VnTrader/run.py index 22c5a54a..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, spreadTrading) +from vnpy.trader.app import (riskManager, ctaStrategy, spreadTrading) #---------------------------------------------------------------------- @@ -43,7 +42,6 @@ def main(): # 添加上层应用 me.addApp(riskManager) - me.addApp(dataRecorder) me.addApp(ctaStrategy) me.addApp(spreadTrading) diff --git a/vnpy/data/shcifco/test.py b/vnpy/data/shcifco/test.py new file mode 100644 index 00000000..15d8e192 --- /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 = '101.231.179.199' + port = '10102' + token = 'testd2cda34b2d317779e812eb84ee4224a6_qpweqf1' + 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, 502, period=PERIOD_1MIN) + + \ No newline at end of file diff --git a/vnpy/data/shcifco/vnshcifco.py b/vnpy/data/shcifco/vnshcifco.py index 94927d9c..568cd0d0 100644 --- a/vnpy/data/shcifco/vnshcifco.py +++ b/vnpy/data/shcifco/vnshcifco.py @@ -5,6 +5,12 @@ import requests HTTP_OK = 200 +PERIOD_1MIN = '1m' +PERIOD_5MIN = '5m' +PERIOD_15MIN = '15m' +PERIOD_60MIN = '60m' +PERIOD_1DAY = '1d' + ######################################################################## class ShcifcoApi(object): @@ -38,8 +44,12 @@ class ShcifcoApi(object): """获取最新Tick""" path = 'lasttick' params = {'ids': symbol} - data = self.getData(path, params) + data = self.getData(path, params) + if not data: + return None + + data = data.split(';')[0] l = data.split(',') d = { 'symbol': l[0], @@ -58,8 +68,12 @@ class ShcifcoApi(object): """获取最新成交价""" path = 'lastprice' params = {'ids': symbol} - data = self.getData(path, params) + data = self.getData(path, params) + if not data: + return None + + data = data.split(';')[0] price = float(data) return price @@ -67,9 +81,13 @@ class ShcifcoApi(object): def getLastBar(self, symbol): """获取最新的一分钟K线数据""" path = 'lastbar' - params = {'ids': symbol} - data = self.getData(path, params) + params = {'id': symbol} + data = self.getData(path, params) + if not data: + return None + + data = data.split(';')[0] l = data.split(',') d = { 'symbol': l[0], @@ -78,18 +96,19 @@ class ShcifcoApi(object): 'high': float(l[3]), 'low': float(l[4]), 'close': float(l[5]), - 'volume': int([6]) + 'volume': int(l[6]), + 'openInterest': int(l[7]) } return d #---------------------------------------------------------------------- def getHisBar(self, symbol, num, date='', period=''): """获取历史K线数据""" - path = 'lastbar' + path = 'hisbar' # 默认参数 params = { - 'ids': symbol, + 'id': symbol, 'num': num } # 可选参数 @@ -99,40 +118,31 @@ class ShcifcoApi(object): 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], - 'time': barData[1], - 'open': float(barData[2]), - 'high': float(barData[3]), - 'low': float(barData[4]), - 'close': float(barData[5]), - 'volume': int([6]) + '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 -if __name__ == "__main__": - ip = '101.231.179.199' - port = '10102' - token = 'testd2cda34b2d317779e812eb84ee4224a6_123456' - - api = ShcifcoApi(ip, port, token) - api.getData(path, params) - - print api.getLastTick('cu1709') - - print api.getLastPrice('cu1709') - - print api.getLastBar('cu1709') - - print api.getHisBar('cu1709', 50) - - \ No newline at end of file diff --git a/vnpy/trader/app/dataRecorder/drEngine.py b/vnpy/trader/app/dataRecorder/drEngine.py index 31f1b5a3..c9af1522 100644 --- a/vnpy/trader/app/dataRecorder/drEngine.py +++ b/vnpy/trader/app/dataRecorder/drEngine.py @@ -77,7 +77,7 @@ class DrEngine(object): # 读取配置 gatewayName = d['gateway'] symbol = d['symbol'] - exchange = d['symbol'] + exchange = d['exchange'] currency = d['currency'] productClass = d['product'] recordTick = d['tick'] diff --git a/vnpy/trader/app/spreadTrading/ST_setting.json b/vnpy/trader/app/spreadTrading/ST_setting.json index b2edf2c1..ba2c1da2 100644 --- a/vnpy/trader/app/spreadTrading/ST_setting.json +++ b/vnpy/trader/app/spreadTrading/ST_setting.json @@ -1,4 +1,25 @@ [ + { + "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", diff --git a/vnpy/trader/app/spreadTrading/stAlgo.py b/vnpy/trader/app/spreadTrading/stAlgo.py index afd4f743..8c35698b 100644 --- a/vnpy/trader/app/spreadTrading/stAlgo.py +++ b/vnpy/trader/app/spreadTrading/stAlgo.py @@ -117,7 +117,8 @@ class StAlgoTemplate(object): #---------------------------------------------------------------------- def writeLog(self, content): """输出算法日志""" - content = ':'.join([self.spreadName, content]) + prefix = ' '.join([self.spreadName, self.algoName]) + content = ':'.join([prefix, content]) self.algoEngine.writeLog(content) #---------------------------------------------------------------------- @@ -287,6 +288,8 @@ class SniperAlgo(StAlgoTemplate): self.hedgeCount = 0 self.active = True + self.writeLog(u'算法启动') + return self.active #---------------------------------------------------------------------- @@ -296,7 +299,9 @@ class SniperAlgo(StAlgoTemplate): self.hedgingTaskDict.clear() self.cancelAllOrders() - self.active = False + self.active = False + self.writeLog(u'算法停止') + return self.active #---------------------------------------------------------------------- @@ -448,7 +453,7 @@ class SniperAlgo(StAlgoTemplate): cancelPassive = False for vtSymbol in self.passiveVtSymbols: - if self.legOrderDict[vtSymbol]: + if vtSymbol in self.legOrderDict and self.legOrderDict[vtSymbol]: self.cancelLegOrder(vtSymbol) cancelPassive = True diff --git a/vnpy/trader/app/spreadTrading/stEngine.py b/vnpy/trader/app/spreadTrading/stEngine.py index 6789da12..1ba5e9b8 100644 --- a/vnpy/trader/app/spreadTrading/stEngine.py +++ b/vnpy/trader/app/spreadTrading/stEngine.py @@ -93,10 +93,10 @@ class StDataEngine(object): activeSetting = setting['activeLeg'] activeLeg = StLeg() - activeLeg.vtSymbol = activeSetting['vtSymbol'] - activeLeg.ratio = activeSetting['ratio'] - activeLeg.multiplier = activeSetting['multiplier'] - activeLeg.payup = activeSetting['payup'] + 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 @@ -110,10 +110,10 @@ class StDataEngine(object): for d in passiveSettingList: passiveLeg = StLeg() - passiveLeg.vtSymbol = d['vtSymbol'] - passiveLeg.ratio = d['ratio'] - passiveLeg.multiplier = d['multiplier'] - passiveLeg.payup = d['payup'] + 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 @@ -176,7 +176,7 @@ class StDataEngine(object): # 更新腿持仓 leg = self.legDict[trade.vtSymbol] direction = trade.direction - offset = trade.offst + offset = trade.offset if direction == DIRECTION_LONG: if offset == OFFSET_OPEN: @@ -212,7 +212,7 @@ class StDataEngine(object): return # 更新腿持仓 - leg = self.legDict[trade.vtSymbol] + leg = self.legDict[pos.vtSymbol] direction = pos.direction if direction == DIRECTION_LONG: @@ -222,7 +222,7 @@ class StDataEngine(object): leg.netPos = leg.longPos - leg.shortPos # 更新价差持仓 - spread = self.vtSymbolSpreadDict[trade.vtSymbol] + spread = self.vtSymbolSpreadDict[pos.vtSymbol] spread.calculatePos() # 推送价差持仓更新 @@ -327,7 +327,7 @@ class StAlgoEngine(object): """处理成交事件""" trade = event.dict_['data'] - algo = self.algoDict.get(trade.vtSymbol, None) + algo = self.vtSymbolAlgoDict.get(trade.vtSymbol, None) if algo: algo.updateTrade(trade) @@ -335,8 +335,8 @@ class StAlgoEngine(object): def processOrderEvent(self, event): """处理委托事件""" order = event.dict_['data'] + algo = self.vtSymbolAlgoDict.get(order.vtSymbol, None) - algo = self.algoDict.get(order.vtSymbol, None) if algo: algo.updateOrder(order) @@ -358,7 +358,7 @@ class StAlgoEngine(object): req.exchange = contract.exchange req.direction = direction req.offset = offset - req.volume = volume + req.volume = int(volume) req.priceType = PRICETYPE_LIMITPRICE if direction == DIRECTION_LONG: @@ -443,7 +443,12 @@ class StAlgoEngine(object): # 创建算法对象 l = self.dataEngine.getAllSpreads() for spread in l: - self.algoDict[spread.name] = SniperAlgo(self, spread) + algo = SniperAlgo(self, spread) + self.algoDict[spread.name] = algo + + # 保存腿代码和算法对象的映射 + for leg in spread.allLegs: + self.vtSymbolAlgoDict[leg.vtSymbol] = algo # 加载配置 f = shelve.open(self.algoFilePath) diff --git a/vnpy/trader/app/spreadTrading/uiStWidget.py b/vnpy/trader/app/spreadTrading/uiStWidget.py index d2613146..91f9c48b 100644 --- a/vnpy/trader/app/spreadTrading/uiStWidget.py +++ b/vnpy/trader/app/spreadTrading/uiStWidget.py @@ -319,7 +319,7 @@ class StActiveButton(QtWidgets.QPushButton): def buttonClicked(self): """改变运行模式""" if self.active: - self.stop + self.stop() else: self.start() @@ -334,7 +334,6 @@ class StActiveButton(QtWidgets.QPushButton): def start(self): """启动""" algoActive = self.algoEngine.startAlgo(self.spreadName) - if algoActive: self.setStarted() diff --git a/vnpy/trader/uiMainWindow.py b/vnpy/trader/uiMainWindow.py index 27d92ad9..27c3f5e7 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 @@ -257,18 +258,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): From 302b6f75d776b6984de48aa327679de65aa9d031 Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Mon, 3 Jul 2017 16:15:29 +0800 Subject: [PATCH 19/19] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E4=BB=B7=E5=B7=AE=E4=BA=A4=E6=98=93=E6=A8=A1=E5=9D=97=E7=9A=84?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=EF=BC=8C=E4=B8=BB=E5=BC=95=E6=93=8E=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=95=B0=E6=8D=AE=E5=BA=93=E5=8A=A0=E8=BD=BD=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=97=B6=E7=9A=84=E6=8E=92=E5=BA=8F=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/DataRecording/runDataRecording.py | 8 +- vnpy/data/shcifco/vnshcifco.py | 3 +- vnpy/trader/app/ctaStrategy/ctaBacktesting.py | 4 +- vnpy/trader/app/ctaStrategy/ctaEngine.py | 4 +- vnpy/trader/app/riskManager/RM_setting.json | 4 +- vnpy/trader/app/spreadTrading/stAlgo.py | 111 +++++++++++++----- vnpy/trader/app/spreadTrading/stBase.py | 2 + vnpy/trader/app/spreadTrading/stEngine.py | 28 ++++- vnpy/trader/app/spreadTrading/uiStWidget.py | 2 +- .../gateway/okcoinGateway/okcoinGateway.py | 1 + vnpy/trader/vtEngine.py | 11 +- 11 files changed, 130 insertions(+), 48 deletions(-) diff --git a/examples/DataRecording/runDataRecording.py b/examples/DataRecording/runDataRecording.py index b4997c14..c05425ea 100644 --- a/examples/DataRecording/runDataRecording.py +++ b/examples/DataRecording/runDataRecording.py @@ -54,11 +54,11 @@ def runParentProcess(): """父进程运行函数""" printLog(u'启动行情记录守护父进程') - DAY_START = time(8, 45) # 日盘启动和停止时间 - DAY_END = time(15, 30) + DAY_START = time(8, 57) # 日盘启动和停止时间 + DAY_END = time(15, 18) - NIGHT_START = time(20, 45) # 夜盘启动和停止时间 - NIGHT_END = time(2, 45) + NIGHT_START = time(20, 57) # 夜盘启动和停止时间 + NIGHT_END = time(2, 33) p = None # 子进程句柄 diff --git a/vnpy/data/shcifco/vnshcifco.py b/vnpy/data/shcifco/vnshcifco.py index 568cd0d0..84f8331e 100644 --- a/vnpy/data/shcifco/vnshcifco.py +++ b/vnpy/data/shcifco/vnshcifco.py @@ -144,5 +144,4 @@ class ShcifcoApi(object): barList.append(d) return barList - - + diff --git a/vnpy/trader/app/ctaStrategy/ctaBacktesting.py b/vnpy/trader/app/ctaStrategy/ctaBacktesting.py index 01b29280..9f3073de 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())) diff --git a/vnpy/trader/app/ctaStrategy/ctaEngine.py b/vnpy/trader/app/ctaStrategy/ctaEngine.py index 319209eb..77464b18 100644 --- a/vnpy/trader/app/ctaStrategy/ctaEngine.py +++ b/vnpy/trader/app/ctaStrategy/ctaEngine.py @@ -337,7 +337,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 +352,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: 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/spreadTrading/stAlgo.py b/vnpy/trader/app/spreadTrading/stAlgo.py index 8c35698b..aa9b4184 100644 --- a/vnpy/trader/app/spreadTrading/stAlgo.py +++ b/vnpy/trader/app/spreadTrading/stAlgo.py @@ -175,7 +175,8 @@ class SniperAlgo(StAlgoTemplate): self.legDict[leg.vtSymbol] = leg self.hedgingTaskDict = {} # 被动腿需要对冲的数量字典 vtSymbol:volume - self.legOrderDict = {} # vtSymbol: list of vtOrderID + self.legOrderDict = {} # vtSymbol: list of vtOrderID + self.orderTradedDict = {} # vtOrderID: tradedVolume #---------------------------------------------------------------------- def updateSpreadTick(self, spread): @@ -229,21 +230,30 @@ class SniperAlgo(StAlgoTemplate): #---------------------------------------------------------------------- def updateTrade(self, trade): """成交更新""" - if not self.active: - return - - if trade.vtSymbol == self.activeVtSymbol: - self.newActiveLegTrade(trade) - else: - self.newPassiveLegTrade(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 @@ -257,7 +267,6 @@ class SniperAlgo(StAlgoTemplate): # 检查若是被动腿,且已经没有未完成委托,则执行对冲 if not orderList and vtSymbol in self.passiveVtSymbols: self.hedgePassiveLeg(vtSymbol) - self.writeLog(u'发出新的被动腿%s对冲单' %vtSymbol) #---------------------------------------------------------------------- def updateTimer(self): @@ -283,9 +292,35 @@ class SniperAlgo(StAlgoTemplate): #---------------------------------------------------------------------- def start(self): """启动""" - if not self.active: - self.quoteCount = 0 - self.hedgeCount = 0 + # 如果已经运行则直接返回状态 + 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'算法启动') @@ -339,6 +374,7 @@ class SniperAlgo(StAlgoTemplate): """发出主动腿""" spread = self.spread + # 首先计算不带正负号的价差委托量 if direction == self.SPREAD_LONG: spreadVolume = min(spread.askVolume, self.maxPosSize - spread.netPos, @@ -359,9 +395,15 @@ class SniperAlgo(StAlgoTemplate): 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 # 重置主动腿报价撤单等待计数 @@ -370,11 +412,16 @@ class SniperAlgo(StAlgoTemplate): """被动腿对冲""" if vtSymbol not in self.hedgingTaskDict: return - legVolume = self.hedgingTaskDict[vtSymbol] + 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): @@ -385,13 +432,19 @@ class SniperAlgo(StAlgoTemplate): self.hedgeCount = 0 # 重置被动腿对冲撤单等待计数 #---------------------------------------------------------------------- - def newActiveLegTrade(self, trade): + def newActiveLegTrade(self, vtSymbol, direction, volume): """新的主动腿成交""" - spread = self.spread + # 输出日志 + self.writeLog(u'主动腿%s成交,方向%s,数量%s' %(vtSymbol, direction, volume)) + + # 将主动腿成交带上方向 + if direction == DIRECTION_SHORT: + volume = -volume # 计算主动腿成交后,对应的价差仓位 + spread = self.spread activeRatio = spread.activeLeg.ratio - spreadVolume = round(trade.volume / activeRatio) # 四舍五入求主动腿成交量对应的价差份数 + spreadVolume = round(volume / activeRatio) # 四舍五入求主动腿成交量对应的价差份数 # 计算价差新仓位,对应的被动腿需要对冲部分 for leg in self.spread.passiveLegs: @@ -402,28 +455,28 @@ class SniperAlgo(StAlgoTemplate): else: self.hedgingTaskDict[leg.vtSymbol] += newHedgingTask - # 输出日志 - self.writeLog(u'主动腿%s成交,方向%s,数量%s' %(trade.vtSymbol, trade.direction, trade.volume)) + # 发出被动腿对冲委托 + self.hedgeAllPassiveLegs() #---------------------------------------------------------------------- - def newPassiveLegTrade(self, trade): + def newPassiveLegTrade(self, vtSymbol, direction, volume): """新的被动腿成交""" - if trade.vtSymbol in self.hedgingTaskDict: + if vtSymbol in self.hedgingTaskDict: # 计算完成的对冲数量 - if trade.direction == DIRECTION_LONG: - hedgedVolume = trade.volume + if direction == DIRECTION_LONG: + hedgedVolume = volume else: - hedgedVolume = -trade.volume + hedgedVolume = -volume # 计算剩余尚未完成的数量 - self.hedgingTaskDict[trade.vtSymbol] -= hedgedVolume + self.hedgingTaskDict[vtSymbol] -= hedgedVolume # 如果已全部完成,则从字典中移除 - if not self.hedgingTaskDict[trade.vtSymbol]: - del self.hedgingTaskDict[trade.vtSymbol] + if not self.hedgingTaskDict[vtSymbol]: + del self.hedgingTaskDict[vtSymbol] # 输出日志 - self.writeLog(u'被动腿%s成交,方向%s,数量%s' %(trade.vtSymbol, trade.direction, trade.volume)) + self.writeLog(u'被动腿%s成交,方向%s,数量%s' %(vtSymbol, direction, volume)) #---------------------------------------------------------------------- def cancelLegOrder(self, vtSymbol): @@ -432,6 +485,8 @@ class SniperAlgo(StAlgoTemplate): return orderList = self.legOrderDict[vtSymbol] + if not orderList: + return for vtOrderID in orderList: self.algoEngine.cancelOrder(vtOrderID) diff --git a/vnpy/trader/app/spreadTrading/stBase.py b/vnpy/trader/app/spreadTrading/stBase.py index 71f25f15..bead63f4 100644 --- a/vnpy/trader/app/spreadTrading/stBase.py +++ b/vnpy/trader/app/spreadTrading/stBase.py @@ -149,6 +149,8 @@ class StSpread(object): self.shortPos = min(self.shortPos, legAdjustedShortPos) # 计算净仓位 + self.longPos = int(self.longPos) + self.shortPos = int(self.shortPos) self.netPos = self.longPos - self.shortPos #---------------------------------------------------------------------- diff --git a/vnpy/trader/app/spreadTrading/stEngine.py b/vnpy/trader/app/spreadTrading/stEngine.py index 1ba5e9b8..808bf565 100644 --- a/vnpy/trader/app/spreadTrading/stEngine.py +++ b/vnpy/trader/app/spreadTrading/stEngine.py @@ -389,25 +389,45 @@ class StAlgoEngine(object): def buy(self, vtSymbol, price, volume, payup=0): """买入""" vtOrderID = self.sendOrder(vtSymbol, DIRECTION_LONG, OFFSET_OPEN, price, volume, payup) - return [vtOrderID] + 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) - return [vtOrderID] + 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) - return [vtOrderID] + 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) - return [vtOrderID] + l = [] + + if vtOrderID: + l.append(vtOrderID) + + return l #---------------------------------------------------------------------- def putAlgoEvent(self, algo): diff --git a/vnpy/trader/app/spreadTrading/uiStWidget.py b/vnpy/trader/app/spreadTrading/uiStWidget.py index 91f9c48b..178b4e55 100644 --- a/vnpy/trader/app/spreadTrading/uiStWidget.py +++ b/vnpy/trader/app/spreadTrading/uiStWidget.py @@ -33,7 +33,7 @@ class StTickMonitor(BasicMonitor): d['askPrice'] = {'chinese':u'卖价', 'cellType':AskCell} d['askVolume'] = {'chinese':u'卖量', 'cellType':AskCell} d['time'] = {'chinese':u'时间', 'cellType':BasicCell} - d['symbol'] = {'chinese':u'代码', 'cellType':BasicCell} + d['symbol'] = {'chinese':u'价差公式', 'cellType':BasicCell} self.setHeaderDict(d) self.setDataKey('name') diff --git a/vnpy/trader/gateway/okcoinGateway/okcoinGateway.py b/vnpy/trader/gateway/okcoinGateway/okcoinGateway.py index 2791c49f..788ceede 100644 --- a/vnpy/trader/gateway/okcoinGateway/okcoinGateway.py +++ b/vnpy/trader/gateway/okcoinGateway/okcoinGateway.py @@ -11,6 +11,7 @@ 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 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: