添加VnTrader中的价差交易模块,完成价差行情的自动计算和显示

This commit is contained in:
vn.py 2017-06-12 17:14:34 +08:00
parent 83fe273188
commit 8c167c392e
13 changed files with 536 additions and 14 deletions

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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
}
]
}
]

View File

@ -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'

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -91,8 +91,6 @@ class CtpGateway(VtGateway):
self.tdConnected = False # 交易API连接状态
self.qryEnabled = False # 循环查询
self.requireAuthentication = False
#----------------------------------------------------------------------
def connect(self):

View File

@ -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):

View File

@ -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接口请求连接事件
EVENT_ERROR = 'eError.' # 错误回报事件

View File

@ -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