初步完成了vn.trader,包含CTP、LTS、WIND的接口,以及CTA策略模块,策略模块的测试尚未全部完成

This commit is contained in:
WOLF 2015-10-16 16:50:44 +08:00
parent 13fbf771b7
commit c3cf86c452
34 changed files with 6166 additions and 236 deletions

3
.gitignore vendored
View File

@ -37,3 +37,6 @@ Release/
# 其他文件
*.dump
*.vssettings
# 不想同步的
*.local

View File

@ -39,8 +39,7 @@ class Config(object):
toke_ = '44ebc0f058981f85382595f9f15f967' + \
'0c7eaf2695de30dd752e8f33e9022baa0'
token = '7c2e59e212dbff90ffd6b382c7afb57' + \
'bc987a99307d382b058af6748f591d723'
token = '575593eb7696aec7339224c0fac2313780d8645f68b77369dcb35f8bcb419a0b'
body = {
'ssl': False,

View File

@ -101,7 +101,7 @@ def test_mktbar_M1_get_interM():
if __name__ == '__main__':
#test_config()
#test_mktbar_D1()
test_mktbar_D1()
#test_bond_D1()
#test_fut_D1()
#test_fund_D1()
@ -116,4 +116,4 @@ if __name__ == '__main__':
#test_mongod_get_all()
#test_mktbar_M1_get_drudgery()
#test_mktbar_M1_get_all()
test_mktbar_M1_get_interM()
#test_mktbar_M1_get_interM()

Binary file not shown.

View File

@ -1414,7 +1414,7 @@ int TdApi::reqOrderInsert(dict req, int nRequestID)
CSecurityFtdcInputOrderField myreq = CSecurityFtdcInputOrderField();
memset(&myreq, 0, sizeof(myreq));
getChar(req, "ContingentCondition", &myreq.ContingentCondition);
getChar(req, "CombOffsetFlag", myreq.CombOffsetFlag);
//getChar(req, "CombOffsetFlag", myreq.CombOffsetFlag);
getChar(req, "UserID", myreq.UserID);
getChar(req, "LimitPrice", myreq.LimitPrice);
getInt(req, "UserForceClose", &myreq.UserForceClose);
@ -1436,6 +1436,19 @@ int TdApi::reqOrderInsert(dict req, int nRequestID)
getInt(req, "RequestID", &myreq.RequestID);
getChar(req, "Direction", &myreq.Direction);
//´¦ÀíCombOffsetFlag
if (req.has_key("CombOffsetFlag"))
{
object o2 = req["CombOffsetFlag"];
extract<string> x2(o2);
if (x2.check())
{
string s2 = x2();
const char *buffer2 = s2.c_str();
myreq.CombOffsetFlag[0] = *buffer2;
}
}
int i = this->api->ReqOrderInsert(&myreq, nRequestID);
return i;
};

View File

@ -0,0 +1,14 @@
{
"Test1": {
"strategyClassName": "TestStrategy",
"vtSymbol": "IF1511"
},
"Test3": {
"strategyClassName": "TestStrategy",
"vtSymbol": "IF1512"
},
"Test2": {
"strategyClassName": "TestStrategy",
"vtSymbol": "IH1511"
}
}

View File

@ -1,7 +1,7 @@
{
"brokerID": "8070",
"tdAddress": "tcp://27.115.57.147:41205",
"password": "123456",
"mdAddress": "tcp://27.115.57.147:41213",
"userID": "750000"
"brokerID": "9999",
"tdAddress": "tcp://180.168.146.187:10000",
"password": "simnow申请",
"mdAddress": "tcp://180.168.212.228:41213",
"userID": "simnow申请"
}

BIN
vn.trader/ContractData.vt Normal file

Binary file not shown.

View File

@ -0,0 +1,11 @@
{
"brokerID": "2011",
"tdAddress": "tcp://211.144.195.163:54505",
"qryAddress": "tcp://211.144.195.163:54506",
"mdAddress": "tcp://211.144.195.163:54513",
"productInfo": "LTS-Test",
"authCode": "N3EHKP4CYHZGM9VJ",
"tdPassword": "华宝证券申请",
"mdPassword": "华宝证券申请",
"userID": "华宝证券申请"
}

15
vn.trader/ctaConstant.py Normal file
View File

@ -0,0 +1,15 @@
# encoding: UTF-8
# CTA引擎中涉及到的交易方向类型
CTAORDER_BUY = u'买开'
CTAORDER_SELL = u'卖平'
CTAORDER_SHORT = u'卖开'
CTAORDER_COVER = u'买平'
# 本地停止单状态
STOPORDER_WAITING = u'等待中'
STOPORDER_CANCELLED = u'已撤销'
STOPORDER_TRIGGERED = u'已触发'
# 本地停止单前缀
STOPORDERPREFIX = 'CtaStopOrder.'

472
vn.trader/ctaEngine.py Normal file
View File

@ -0,0 +1,472 @@
# encoding: UTF-8
from datetime import datetime
import json
from collections import OrderedDict
from eventEngine import *
from vtConstant import *
from vtGateway import VtSubscribeReq, VtOrderReq, VtCancelOrderReq, VtLogData
from ctaConstant import *
from ctaStrategies import strategyClassDict
########################################################################
class StopOrder(object):
"""本地停止单"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
self.vtSymbol = EMPTY_STRING
self.orderType = EMPTY_UNICODE
self.price = EMPTY_FLOAT
self.volume = EMPTY_INT
self.strategy = None # 下停止单的策略对象
self.stopOrderID = EMPTY_STRING # 停止单的本地编号
self.status = EMPTY_STRING # 停止单状态
########################################################################
class CtaBarData(object):
"""K线数据"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
self.vtSymbol = EMPTY_STRING # vt系统代码
self.symbol = EMPTY_STRING # 代码
self.exchange = EMPTY_STRING # 交易所
self.open = EMPTY_FLOAT # OHLC
self.high = EMPTY_FLOAT
self.low = EMPTY_FLOAT
self.close = EMPTY_FLOAT
self.date = EMPTY_STRING # bar开始的时间日期
self.time = EMPTY_STRING # 时间
self.datetime = None # python的datetime时间对象
self.volume = EMPTY_INT # 成交量
self.openInterest = EMPTY_INT # 持仓量
########################################################################
class CtaTickData(object):
"""Tick数据"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
self.vtSymbol = EMPTY_STRING # vt系统代码
self.symbol = EMPTY_STRING # 合约代码
self.exchange = EMPTY_STRING # 交易所代码
# 成交数据
self.lastPrice = EMPTY_FLOAT # 最新成交价
self.volume = EMPTY_INT # 最新成交量
self.openInterest = EMPTY_INT # 持仓量
self.upperLimit = EMPTY_FLOAT # 涨停价
self.lowerLimit = EMPTY_FLOAT # 跌停价
# tick的时间
self.date = EMPTY_STRING # 日期
self.time = EMPTY_STRING # 时间
self.datetime = None # python的datetime时间对象
# 五档行情
self.bidPrice1 = EMPTY_FLOAT
self.bidPrice2 = EMPTY_FLOAT
self.bidPrice3 = EMPTY_FLOAT
self.bidPrice4 = EMPTY_FLOAT
self.bidPrice5 = EMPTY_FLOAT
self.askPrice1 = EMPTY_FLOAT
self.askPrice2 = EMPTY_FLOAT
self.askPrice3 = EMPTY_FLOAT
self.askPrice4 = EMPTY_FLOAT
self.askPrice5 = EMPTY_FLOAT
self.bidVolume1 = EMPTY_INT
self.bidVolume2 = EMPTY_INT
self.bidVolume3 = EMPTY_INT
self.bidVolume4 = EMPTY_INT
self.bidVolume5 = EMPTY_INT
self.askVolume1 = EMPTY_INT
self.askVolume2 = EMPTY_INT
self.askVolume3 = EMPTY_INT
self.askVolume4 = EMPTY_INT
self.askVolume5 = EMPTY_INT
########################################################################
class CtaEngine(object):
"""CTA策略引擎"""
settingFileName = 'CTA_setting.json'
#----------------------------------------------------------------------
def __init__(self, mainEngine, eventEngine, dataEngine):
"""Constructor"""
self.mainEngine = mainEngine
self.eventEngine = eventEngine
self.dataEngine = dataEngine
# 保存策略对象的字典
# key为策略名称value为策略对象注意策略名称不允许重复
self.strategyDict = {}
# 保存vtSymbol和策略对象映射的字典用于推送tick数据
# 由于可能多个strategy交易同一个vtSymbol因此key为vtSymbol
# value为包含所有相关strategy对象的list
self.tickStrategyDict = {}
# 保存vtOrderID和strategy对象映射的字典用于推送order和trade数据
# key为vtOrderIDvalue为strategy对象
self.orderStrategyDict = {}
# 本地停止单编号计数
self.stopOrderCount = 0
# stopOrderID = STOPORDERPREFIX + str(stopOrderCount)
# 本地停止单字典
# key为stopOrderIDvalue为stopOrder对象
self.stopOrderDict = {} # 停止单撤销后不会从本字典中删除
self.workingStopOrderDict = {} # 停止单撤销后会从本字典中删除
# 注册事件监听
self.registerEvent()
#----------------------------------------------------------------------
def sendOrder(self, vtSymbol, orderType, price, volume, strategy):
"""发单"""
contract = self.dataEngine.getContract(vtSymbol)
req = VtOrderReq()
req.symbol = contract.symbol
req.exchange = contract.exchange
req.price = price
req.volume = volume
# 设计为CTA引擎发出的委托只允许使用限价单
req.priceType = PRICETYPE_LIMITPRICE
# CTA委托类型映射
if orderType == CTAORDER_BUY:
req.direction = DIRECTION_LONG
req.offset = OFFSET_OPEN
elif orderType == CTAORDER_SELL:
req.direction = DIRECTION_SHORT
req.offset = OFFSET_CLOSE
elif orderType == CTAORDER_SHORT:
req.direction = DIRECTION_SHORT
req.offset = OFFSET_OPEN
elif orderType == CTAORDER_COVER:
req.direction = DIRECTION_LONG
req.offset = OFFSET_CLOSE
vtOrderID = self.mainEngine.sendOrder(req) # 发单
self.orderDict[vtOrderID] = strategy # 保存vtOrderID和策略的映射关系
return vtOrderID
#----------------------------------------------------------------------
def cancelOrder(self, vtOrderID):
"""撤单"""
# 查询报单对象
order = self.dataEngine.getOrder(vtOrderID)
# 如果查询成功
if order:
# 检查是否报单还有效,只有有效时才发出撤单指令
orderFinished = (order.status==STATUS_ALLTRADED or order.status==STATUS_CANCELLED)
if not orderFinished:
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 sendStopOrder(self, vtSymbol, orderType, price, volume, strategy):
"""发停止单(本地实现)"""
self.stopOrderCount += 1
stopOrderID = STOPORDERPREFIX + str(self.stopOrderCount)
so = StopOrder()
so.vtSymbol = vtSymbol
so.orderType = orderType
so.price = price
so.volume = volume
so.strategy = strategy
so.stopOrderID = stopOrderID
so.status = STOPORDER_WAITING
# 保存stopOrder对象到字典中
self.stopOrderDict[stopOrderID] = so
self.workingStopOrderDict[stopOrderID] = so
return stopOrderID
#----------------------------------------------------------------------
def cancelStopOrder(self, stopOrderID):
"""撤销停止单"""
# 检查停止单是否存在
if stopOrderID in self.workingStopOrderDict:
so = self.workingStopOrderDict[stopOrderID]
so.status = STOPORDER_CANCELLED
del self.workingStopOrderDict[stopOrderID]
#----------------------------------------------------------------------
def processStopOrder(self, tick):
"""收到行情后处理本地停止单(检查是否要立即发出)"""
vtSymbol = tick.vtSymbol
# 首先检查是否有策略交易该合约
if vtSymbol in self.tickStrategyDict:
# 遍历等待中的停止单,检查是否会被触发
for so in self.workingStopOrderDict.values():
if so.vtSymbol == vtSymbol:
longTriggered = so.direction==DIRECTION_LONG and tick.lastPrice>=so.price # 多头停止单被触发
shortTriggered = so.direction==DIRECTION_SHORT and tick.lasatPrice<=so.price # 空头停止单被触发
if longTriggered or shortTriggered:
# 买入和卖出分别以涨停跌停价发单(模拟市价单)
if so.direction==DIRECTION_LONG:
price = tick.upperLimit
else:
price = tick.lowerLimit
so.status = STOPORDER_TRIGGERED
self.sendOrder(so.vtSymbol, so.orderType, price, so.volume, so.strategy)
del self.workingStopOrderDict[so.stopOrderID]
#----------------------------------------------------------------------
def procecssTickEvent(self, event):
"""处理行情推送"""
tick = event.dict_['data']
# 收到tick行情后先处理本地停止单检查是否要立即发出
self.processStopOrder(tick)
# 推送tick到对应的策略对象进行处理
if tick.vtSymbol in self.tickStrategyDict:
# 将vtTickData数据转化为ctaTickData
ctaTick = CtaTickData()
d = ctaTick.__dict__
for key in d.keys():
if key != 'datetime':
d[key] = tick.__getattribute__(key)
# 添加datetime字段
ctaTick.datetime = datetime.strptime(' '.join([tick.date, tick.time]), '%Y%m%d %H:%M:%S.%f')
# 逐个推送到策略对象中
l = self.tickStrategyDict[tick.vtSymbol]
for strategy in l:
strategy.onTick(tick)
#----------------------------------------------------------------------
def processOrderEvent(self, event):
"""处理委托推送"""
order = event.dict_['data']
if order.vtOrderID in self.orderStrategyDict:
strategy = self.orderStrategyDict[order.vtOrderID]
strategy.onOrder(order)
#----------------------------------------------------------------------
def processTradeEvent(self, event):
"""处理成交推送"""
trade = event.dict_['data']
if trade.vtOrderID in self.orderStrategyDict:
strategy = self.orderStrategyDict[order.vtOrderID]
strategy.onTrade(trade)
#----------------------------------------------------------------------
def registerEvent(self):
"""注册事件监听"""
self.eventEngine.register(EVENT_TICK, self.procecssTickEvent)
self.eventEngine.register(EVENT_ORDER, self.processOrderEvent)
self.eventEngine.register(EVENT_TRADE, self.processTradeEvent)
#----------------------------------------------------------------------
def insertData(self, dbName, collectionName, data):
"""插入数据到数据库这里的data可以是CtaTickData或者CtaBarData"""
self.mainEngine.dbInsert(dbName, collectionName, data.__dict__)
#----------------------------------------------------------------------
def loadBar(self, dbName, collectionName, startDate):
"""从数据库中读取Bar数据startDate是datetime对象"""
d = {'datetime':{'$gte':startDate}}
cursor = self.mainEngine.dbQuery(dbName, collectionName, d)
l = []
for d in cursor:
bar = CtaBarData()
bar.__dict__ = d
l.append(bar)
return l
#----------------------------------------------------------------------
def loadTick(self, dbName, collectionName, startDate):
"""从数据库中读取Tick数据startDate是datetime对象"""
d = {'datetime':{'$gte':startDate}}
cursor = self.mainEngine.dbQuery(dbName, collectionName, d)
l = []
for d in cursor:
tick = CtaTickData()
tick.__dict__ = d
l.append(tick)
return l
#----------------------------------------------------------------------
def getToday(self):
"""获取代表今日的datetime对象"""
today = datetime.today()
today = today.replace(hour=0, minute=0, second=0, microsecond=0)
return today
#----------------------------------------------------------------------
def writeCtaLog(self, content):
"""快速发出CTA模块日志事件"""
log = VtLogData()
log.logContent = content
event = Event(type_=EVENT_CTA_LOG)
event.dict_['data'] = log
self.eventEngine.put(event)
#----------------------------------------------------------------------
def initStrategy(self, name, strategyClass, paramDict=None):
"""初始化策略"""
# 防止策略重名
if name not in self.strategyDict:
# 创建策略对象
strategy = strategyClass(self, name, paramDict)
self.strategyDict[name] = strategy
# 保存Tick映射关系
if strategy.vtSymbol in self.tickStrategyDict:
l = self.tickStrategyDict[strategy.vtSymbol]
else:
l = []
self.tickStrategyDict[strategy.vtSymbol] = l
l.append(strategy)
# 订阅合约
contract = self.dataEngine.getContract(strategy.vtSymbol)
if contract:
req = VtSubscribeReq()
req.symbol = contract.symbol
req.exchange = contract.exchange
self.mainEngine.subscribe(req, contract.gatewayName)
else:
self.writeCtaLog(u'存在策略对象重名:' + name)
#---------------------------------------------------------------------
def startStrategy(self, name):
"""启动策略"""
if name in self.strategyDict:
strategy = self.strategyDict[name]
if not strategy.trading:
strategy.trading = True
strategy.start()
else:
self.writeCtaLog(u'策略对象不存在:' + name)
#----------------------------------------------------------------------
def stopStrategy(self, name):
"""停止策略"""
if name in self.strategyDict:
strategy = self.strategyDict[name]
if strategy.trading:
strategy.trading = False
strategy.stop()
# 对该策略发出的所有限价单进行撤单
for vtOrderID, s in self.orderStrategyDict.items():
if s is strategy:
self.cancelOrder(vtOrderID)
# 对该策略发出的所有本地停止单撤单
for stopOrderID, so in self.workingStopOrderDict.items():
if so.strategy is strategy:
self.cancelStopOrder(stopOrderID)
else:
self.writeCtaLog(u'策略对象不存在:' + name)
#----------------------------------------------------------------------
def saveStrategySetting(self):
"""保存引擎中的策略配置"""
with open(self.settingFileName, 'w') as f:
d = {}
for name, strategy in self.strategyDict.items():
setting = {}
setting['strategyClassName'] = strategy.strategyClassName
for param in strategy.paramList:
setting[param] = strategy.__getattribute__(param)
d[name] = setting
jsonD = json.dumps(d, indent=4)
f.write(jsonD)
#----------------------------------------------------------------------
def loadStrategySetting(self):
"""读取引擎中的策略配置"""
with open(self.settingFileName) as f:
d = json.load(f)
for name, setting in d.items():
strategyClassName = setting['strategyClassName']
if strategyClassName in strategyClassDict:
strategyClass = strategyClassDict[strategyClassName]
self.initStrategy(name, strategyClass, setting)
else:
self.writeCtaLog(u'无法找到策略类:' + strategyClassName)
break
#----------------------------------------------------------------------
def getStrategyVar(self, name):
"""获取策略当前的变量字典"""
if name in self.strategyDict:
strategy = self.strategyDict[name]
d = strategy.__dict__
varDict = OrderedDict()
for key in strategy.varList:
if key in d:
varDict[key] = d[key]
return varDict
else:
self.writeCtaLog(u'策略对象不存在:' + name)
return None
#----------------------------------------------------------------------
def getStrategyParam(self, name):
"""获取策略的参数字典"""
if name in self.strategyDict:
strategy = self.strategyDict[name]
d = strategy.__dict__
paramDict = OrderedDict()
for key in strategy.paramList:
if key in d:
paramDict[key] = d[key]
return paramDict
else:
self.writeCtaLog(u'策略对象不存在:' + name)
return None

View File

@ -0,0 +1,10 @@
# encoding: UTF-8
'''
在本文件中引入所有希望在系统中使用的策略类
'''
from ctaStrategyTemplate import TestStrategy
strategyClassDict = {}
strategyClassDict[u'TestStrategy'] = TestStrategy

View File

@ -0,0 +1,233 @@
# encoding: UTF-8
from vtConstant import *
from ctaConstant import *
########################################################################
class CtaStrategyTemplate(object):
"""CTA策略模板"""
# 策略类的名称
strategyClassName = 'Template'
# 参数列表,保存了参数的名称
paramList = ['vtSymbol']
# 变量列表,保存了变量的名称
varList = ['trading']
#----------------------------------------------------------------------
def __init__(self, ctaEngine, name, setting=None):
"""Constructor"""
self.ctaEngine = ctaEngine
self.name = name
self.vtSymbol = EMPTY_STRING # 交易的合约vt系统代码
self.tickDbName = EMPTY_STRING # tick数据库名称
self.barDbName = EMPTY_STRING # bar数据库名称
self.trading = False # 控制是否启动交易
self.init() # 初始化策略
if setting:
self.setParam(setting)
#----------------------------------------------------------------------
def init(self):
"""初始化策略(必须由用户继承实现)"""
raise NotImplementedError
#----------------------------------------------------------------------
def start(self):
"""启动策略(必须由用户继承实现)"""
raise NotImplementedError
#----------------------------------------------------------------------
def stop(self):
"""停止策略(必须由用户继承实现)"""
raise NotImplementedError
#----------------------------------------------------------------------
def onTick(self, tick):
"""收到行情TICK推送必须由用户继承实现"""
raise NotImplementedError
#----------------------------------------------------------------------
def onOrder(self, order):
"""收到委托变化推送(必须由用户继承实现)"""
raise NotImplementedError
#----------------------------------------------------------------------
def onTrade(self, trade):
"""收到成交推送(必须由用户继承实现)"""
raise NotImplementedError
#----------------------------------------------------------------------
def onBar(self, bar):
"""收到Bar推送必须由用户继承实现"""
raise NotImplementedError
#----------------------------------------------------------------------
def buy(self, price, volume, stop=False):
"""买开"""
# 如果stop为True则意味着发本地停止单
if self.trading:
if stop:
orderID = self.ctaEngine.sendStopOrder(self.vtSymbol, CTAORDER_BUY, price, volume, self)
else:
orderID = self.ctaEngine.sendOrder(self.vtSymbol, CTAORDER_BUY, price, volume, self)
return orderID
else:
return None
#----------------------------------------------------------------------
def sell(self, price, volume, stop=False):
"""卖平"""
# 如果stop为True则意味着发本地停止单
if self.trading:
if stop:
orderID = self.ctaEngine.sendStopOrder(self.vtSymbol, CTAORDER_SELL, price, volume, self)
else:
orderID = self.ctaEngine.sendOrder(self.vtSymbol, CTAORDER_SELL, price, volume, self)
return orderID
else:
return None
#----------------------------------------------------------------------
def short(self, price, volume, stop=False):
"""卖开"""
# 如果stop为True则意味着发本地停止单
if self.trading:
if stop:
orderID = self.ctaEngine.sendStopOrder(self.vtSymbol, CTAORDER_SHORT, price, volume, self)
else:
orderID = self.ctaEngine.sendOrder(self.vtSymbol, CTAORDER_SHORT, price, volume, self)
return orderID
else:
return None
#----------------------------------------------------------------------
def cover(self, price, volume, stop=False):
"""买平"""
if self.trading:
# 如果stop为True则意味着发本地停止单
if stop:
orderID = self.ctaEngine.sendStopOrder(self.vtSymbol, CTAORDER_COVER, price, volume, self)
else:
orderID = self.ctaEngine.sendOrder(self.vtSymbol, CTAORDER_COVER, price, volume, self)
return orderID
else:
return None
#----------------------------------------------------------------------
def cancelOrder(self, orderID):
"""撤单"""
if STOPORDERPREFIX in orderID:
self.ctaEngine.cancelStopOrder(orderID)
else:
self.ctaEngine.cancelOrder(orderID)
#----------------------------------------------------------------------
def insertTick(self, tick):
"""向数据库中插入tick数据"""
self.ctaEngine.insertData(self.tickDbName, self.vtSymbol, tick)
#----------------------------------------------------------------------
def insertBar(self, bar):
"""向数据库中插入bar数据"""
self.ctaEngine.insertData(self.barDbName, self.vtSymbol, bar)
#----------------------------------------------------------------------
def loadTick(self, startDate):
"""读取tick数据"""
return self.ctaEngine.loadTick(self.tickDbName, self.vtSymbol, startDate)
#----------------------------------------------------------------------
def loadBar(self, startDate):
"""读取bar数据"""
return self.ctaEngine.loadBar(self.barDbName, self.vtSymbol, startDate)
#----------------------------------------------------------------------
def setParam(self, setting):
"""设置参数"""
d = self.__dict__
for key in self.paramList:
if key in setting:
d[key] = setting[key]
#----------------------------------------------------------------------
def getToday(self):
"""查询当前日期"""
return self.ctaEngine.getToday()
#----------------------------------------------------------------------
def writeCtaLog(self, content):
"""记录CTA日志"""
self.ctaEngine.writeCtaLog(content)
########################################################################
class TestStrategy(CtaStrategyTemplate):
"""测试策略"""
#----------------------------------------------------------------------
def __init__(self, ctaEngine, name, setting=None):
"""Constructor"""
super(TestStrategy, self).__init__(ctaEngine, name, setting)
self.strategyClassName = 'TestStrategy'
self.author = u'用Python的交易员' # 作者
self.pos = EMPTY_INT # 持仓
self.lastPrice = EMPTY_FLOAT # 最新价
# 参数和变量列表设置
self.paramList.append(u'author')
self.varList.append('pos')
self.varList.append('lastPrice')
#----------------------------------------------------------------------
def init(self):
"""初始化策略(必须由用户继承实现)"""
self.writeCtaLog(u'测试策略%s初始化' %self.name)
#----------------------------------------------------------------------
def start(self):
"""启动策略(必须由用户继承实现)"""
self.writeCtaLog(u'测试策略%s启动' %self.name)
#----------------------------------------------------------------------
def stop(self):
"""停止策略(必须由用户继承实现)"""
self.writeCtaLog(u'测试策略%s停止' %self.name)
#----------------------------------------------------------------------
def onTick(self, tick):
"""收到行情TICK推送必须由用户继承实现"""
self.writeCtaLog(u'测试策略%s收到Tick' %self.name)
self.lastPrice = tick.lastPrice
#----------------------------------------------------------------------
def onOrder(self, order):
"""收到委托变化推送(必须由用户继承实现)"""
self.writeCtaLog(u'onOrder不会被调用')
#----------------------------------------------------------------------
def onTrade(self, trade):
"""收到成交推送(必须由用户继承实现)"""
self.writeCtaLog(u'onTrade不会被调用')
#----------------------------------------------------------------------
def onBar(self, bar):
"""收到Bar推送必须由用户继承实现"""
self.writeCtaLog(u'测试策略%s收到Bar' %self.name)

View File

@ -1,5 +1,13 @@
# encoding: UTF-8
'''
vn.ctp的gateway接入
考虑到现阶段大部分CTP中的ExchangeID字段返回的都是空值
vtSymbol直接使用symbol
'''
import os
import json
@ -8,6 +16,47 @@ from vnctptd import TdApi
from ctpDataType import *
from vtGateway import *
# 以下为一些VT类型和CTP类型的映射字典
# 价格类型映射
priceTypeMap = {}
priceTypeMap[PRICETYPE_LIMITPRICE] = defineDict["THOST_FTDC_OPT_LimitPrice"]
priceTypeMap[PRICETYPE_MARKETPRICE] = defineDict["THOST_FTDC_OPT_AnyPrice"]
priceTypeMapReverse = {v: k for k, v in priceTypeMap.items()}
# 方向类型映射
directionMap = {}
directionMap[DIRECTION_LONG] = defineDict['THOST_FTDC_D_Buy']
directionMap[DIRECTION_SHORT] = defineDict['THOST_FTDC_D_Sell']
directionMapReverse = {v: k for k, v in directionMap.items()}
# 开平类型映射
offsetMap = {}
offsetMap[OFFSET_OPEN] = defineDict['THOST_FTDC_OF_Open']
offsetMap[OFFSET_CLOSE] = defineDict['THOST_FTDC_OF_Close']
offsetMap[OFFSET_CLOSETODAY] = defineDict['THOST_FTDC_OF_CloseToday']
offsetMap[OFFSET_CLOSESYESTERDAY] = defineDict['THOST_FTDC_OF_CloseYesterday']
offsetMapReverse = {v:k for k,v in offsetMap.items()}
# 交易所类型映射
exchangeMap = {}
#exchangeMap[EXCHANGE_CFFEX] = defineDict['THOST_FTDC_EIDT_CFFEX']
#exchangeMap[EXCHANGE_SHFE] = defineDict['THOST_FTDC_EIDT_SHFE']
#exchangeMap[EXCHANGE_CZCE] = defineDict['THOST_FTDC_EIDT_CZCE']
#exchangeMap[EXCHANGE_DCE] = defineDict['THOST_FTDC_EIDT_DCE']
exchangeMap[EXCHANGE_CFFEX] = 'CFFEX'
exchangeMap[EXCHANGE_SHFE] = 'SHFE'
exchangeMap[EXCHANGE_CZCE] = 'CZCE'
exchangeMap[EXCHANGE_DCE] = 'DCE'
exchangeMap[EXCHANGE_UNKNOWN] = ''
exchangeMapReverse = {v:k for k,v in exchangeMap.items()}
# 持仓类型映射
posiDirectionMap = {}
posiDirectionMap[DIRECTION_NET] = defineDict["THOST_FTDC_PD_Net"]
posiDirectionMap[DIRECTION_LONG] = defineDict["THOST_FTDC_PD_Long"]
posiDirectionMap[DIRECTION_SHORT] = defineDict["THOST_FTDC_PD_Short"]
posiDirectionMapReverse = {v:k for k,v in posiDirectionMap.items()}
########################################################################
class CtpGateway(VtGateway):
@ -24,6 +73,8 @@ class CtpGateway(VtGateway):
self.mdConnected = False # 行情API连接状态登录完成后为True
self.tdConnected = False # 交易API连接状态
self.qryEnabled = False # 是否要启动循环查询
#----------------------------------------------------------------------
def connect(self):
"""连接"""
@ -57,10 +108,13 @@ class CtpGateway(VtGateway):
self.mdApi.connect(userID, password, brokerID, mdAddress)
self.tdApi.connect(userID, password, brokerID, tdAddress)
# 初始化并启动查询
self.initQuery()
#----------------------------------------------------------------------
def subscribe(self, subscribeReq):
"""订阅行情"""
self.mdApi(subscribeReq)
self.mdApi.subscribe(subscribeReq)
#----------------------------------------------------------------------
def sendOrder(self, orderReq):
@ -85,9 +139,53 @@ class CtpGateway(VtGateway):
#----------------------------------------------------------------------
def close(self):
"""关闭"""
if self.mdConnected:
self.mdApi.close()
if self.tdConnected:
self.tdApi.close()
#----------------------------------------------------------------------
def initQuery(self):
"""初始化连续查询"""
if self.qryEnabled:
# 需要循环的查询函数列表
self.qryFunctionList = [self.getAccount, self.getPosition]
self.qryCount = 0 # 查询触发倒计时
self.qryTrigger = 2 # 查询触发点
self.qryNextFunction = 0 # 上次运行的查询函数索引
self.startQuery()
#----------------------------------------------------------------------
def query(self, event):
"""注册到事件处理引擎上的查询函数"""
self.qryCount += 1
if self.qryCount > self.qryTrigger:
# 清空倒计时
self.qryCount = 0
# 执行查询函数
function = self.qryFunctionList[self.qryNextFunction]
function()
# 计算下次查询函数的索引如果超过了列表长度则重新设为0
self.qryNextFunction += 1
if self.qryNextFunction == len(self.qryFunctionList):
self.qryNextFunction = 0
#----------------------------------------------------------------------
def startQuery(self):
"""启动连续查询"""
self.eventEngine.register(EVENT_TIMER, self.query)
#----------------------------------------------------------------------
def setQryEnabled(self, qryEnabled):
"""设置是否要启动循环查询"""
self.qryEnabled = qryEnabled
########################################################################
class CtpMdApi(MdApi):
@ -106,7 +204,6 @@ class CtpMdApi(MdApi):
self.connectionStatus = False # 连接状态
self.loginStatus = False # 登录状态
self.subscribedSymbols = set() # 已订阅合约代码
self.userID = EMPTY_STRING # 账号
@ -123,7 +220,6 @@ class CtpMdApi(MdApi):
log.gatewayName = self.gatewayName
log.logContent = u'行情服务器连接成功'
self.gateway.onLog(log)
self.login()
#----------------------------------------------------------------------
@ -162,6 +258,7 @@ class CtpMdApi(MdApi):
self.gateway.mdConnected = True
log = VtLogData()
log.gatewayName = self.gatewayName
log.logContent = u'行情服务器登录完成'
self.gateway.onLog(log)
@ -214,18 +311,25 @@ class CtpMdApi(MdApi):
def onRtnDepthMarketData(self, data):
"""行情推送"""
tick = VtTickData()
tick.gatewayName = self.gatewayName
tick.symbol = data['InstrumentID']
tick.vtSymbol = '.'.join([self.gatewayName, tick.symbol])
tick.exchange = exchangeMapReverse.get(data['ExchangeID'], u'未知')
tick.vtSymbol = tick.symbol #'.'.join([tick.symbol, EXCHANGE_UNKNOWN])
tick.lastPrice = data['LastPrice']
tick.volume = data['Volume']
tick.openInterest = data['OpenInterest']
tick.tickTime = '.'.join([data['UpdateTime'], str(data['UpdateMillisec']/100)])
tick.time = '.'.join([data['UpdateTime'], str(data['UpdateMillisec']/100)])
tick.date = data['TradingDay']
tick.openPrice = data['OpenPrice']
tick.highPrice = data['HighestPrice']
tick.lowPrice = data['LowestPrice']
tick.preClosePrice = data['PreClosePrice']
tick.upperLimit = data['UpperLimitPrice']
tick.lowerLimit = data['LowerLimitPrice']
# CTP只有一档行情
tick.bidPrice1 = data['BidPrice1']
@ -280,7 +384,10 @@ class CtpMdApi(MdApi):
#----------------------------------------------------------------------
def subscribe(self, subscribeReq):
"""订阅合约"""
self.subscribeMarketData(subscribeReq.symbol)
# 这里的设计是,如果尚未登录就调用了订阅方法
# 则先保存订阅请求,登录完成后会自动订阅
if self.loginStatus:
self.subscribeMarketData(str(subscribeReq.symbol))
self.subscribedSymbols.add(subscribeReq)
#----------------------------------------------------------------------
@ -305,27 +412,6 @@ class CtpMdApi(MdApi):
class CtpTdApi(TdApi):
"""CTP交易API实现"""
# 以下为一些VT类型和CTP类型的映射字典
# 价格类型映射
priceTypeMap = {}
priceTypeMap[PRICETYPE_LIMITPRICE] = defineDict["THOST_FTDC_OPT_LimitPrice"]
priceTypeMap[PRICETYPE_MARKETPRICE] = defineDict["THOST_FTDC_OPT_AnyPrice"]
priceTypeMapReverse = {v: k for k, v in priceTypeMap.items()}
# 方向类型映射
directionMap = {}
directionMap[DIRECTION_LONG] = defineDict['THOST_FTDC_D_Buy']
directionMap[DIRECTION_SHORT] = defineDict['THOST_FTDC_D_Sell']
directionMapReverse = {v: k for k, v in directionMap.items()}
# 开平类型映射
offsetMap = {}
offsetMap[OFFSET_OPEN] = defineDict['THOST_FTDC_OF_Open']
offsetMap[OFFSET_CLOSE] = defineDict['THOST_FTDC_OF_Close']
offsetMap[OFFSET_CLOSETODAY] = defineDict['THOST_FTDC_OF_CloseToday']
offsetMap[OFFSET_CLOSESYESTERDAY] = defineDict['THOST_FTDC_OF_CloseYesterday']
offsetMapReverse = {v:k for k,v in offsetMap.items()}
#----------------------------------------------------------------------
def __init__(self, gateway):
"""API对象的初始化函数"""
@ -345,6 +431,9 @@ class CtpTdApi(TdApi):
self.brokerID = EMPTY_STRING # 经纪商代码
self.address = EMPTY_STRING # 服务器地址
self.frontID = EMPTY_INT # 前置机编号
self.sessionID = EMPTY_INT # 会话编号
#----------------------------------------------------------------------
def onFrontConnected(self):
"""服务器连接"""
@ -384,6 +473,8 @@ class CtpTdApi(TdApi):
"""登陆回报"""
# 如果登录成功,推送日志信息
if error['ErrorID'] == 0:
self.frontID = str(data['FrontID'])
self.sessionID = str(data['SessionID'])
self.loginStatus = True
self.gateway.mdConnected = True
@ -536,17 +627,13 @@ class CtpTdApi(TdApi):
# 保存代码
pos.symbol = data['InstrumentID']
pos.vtSymbol = '.'.join([self.gatewayName, pos.symbol])
pos.vtSymbol = pos.symbol # 这里因为data中没有ExchangeID这个字段
# 方向和持仓冻结数量
if data['PosiDirection'] == '1':
pos.direction = DIRECTION_NET
pos.direction = posiDirectionMapReverse.get(data['PosiDirection'], '')
if pos.direction == DIRECTION_NET or pos.direction == DIRECTION_LONG:
pos.frozen = data['LongFrozen']
if data['PosiDirection'] == '2':
pos.direction = DIRECTION_LONG
pos.frozen = data['LongFrozen']
elif data['PosiDirection'] == '3':
pos.direction = DIRECTION_SHORT
elif pos.direction == DIRECTION_SHORT:
pos.frozen = data['ShortFrozen']
# 持仓量
@ -583,7 +670,7 @@ class CtpTdApi(TdApi):
# 这里的balance和快期中的账户不确定是否一样需要测试
account.balance = (data['PreBalance'] - data['PreCredit'] - data['PreMortgage'] +
data['Mortgage'] - data['Withdraw'] + data['Deposit'] +
data['ClostProfit'] + data['PositionProfit'] + data['CashIn'] -
data['CloseProfit'] + data['PositionProfit'] + data['CashIn'] -
data['Commission'])
# 推送
@ -626,8 +713,9 @@ class CtpTdApi(TdApi):
contract.gatewayName = self.gatewayName
contract.symbol = data['InstrumentID']
contract.vtSymbol = '.'.join([self.gatewayName, contract.symbol])
contract.name = data['InstrumentName']
contract.exchange = exchangeMapReverse[data['ExchangeID']]
contract.vtSymbol = contract.symbol #'.'.join([contract.symbol, contract.exchange])
contract.name = data['InstrumentName'].decode('GBK')
# 合约数值
contract.size = data['VolumeMultiple']
@ -637,21 +725,29 @@ class CtpTdApi(TdApi):
# 合约类型
if data['ProductClass'] == '1':
contract.productClass == PRODUCT_FUTURES
contract.productClass = PRODUCT_FUTURES
elif data['ProductClass'] == '2':
contract.productClass = PRODUCT_OPTION
elif data['ProductClass'] == '3':
contract.productClass = PRODUCT_COMBINATION
else:
contract.productClass = PRODUCT_UNKNOWN
# 期权类型
if data['OptionType'] == '1':
if data['OptionsType'] == '1':
contract.optionType = OPTION_CALL
elif data['OptionType'] == '2':
elif data['OptionsType'] == '2':
contract.optionType = OPTION_PUT
# 推送
self.gateway.onContract(contract)
if last:
log = VtLogData()
log.gatewayName = self.gatewayName
log.logContent = u'交易合约信息获取完成'
self.gateway.onLog(log)
#----------------------------------------------------------------------
def onRspQryDepthMarketData(self, data, error, n, last):
""""""
@ -779,10 +875,10 @@ class CtpTdApi(TdApi):
# 保存代码和报单号
order.symbol = data['InstrumentID']
order.vtSymbol = '.'.join([self.gatewayName, order.symbol])
order.exchange = exchangeMapReverse[data['ExchangeID']]
order.vtSymbol = order.symbol #'.'.join([order.symbol, order.exchange])
order.orderID = data['OrderRef']
order.vtOrderID = '.'.join([self.gatewayName, order.orderID])
# 方向
if data['Direction'] == '0':
@ -821,6 +917,12 @@ class CtpTdApi(TdApi):
order.frontID = data['FrontID']
order.sessionID = data['SessionID']
# CTP的报单号一致性维护需要基于frontID, sessionID, orderID三个字段
# 但在本接口设计中已经考虑了CTP的OrderRef的自增性避免重复
# 唯一可能出现OrderRef重复的情况是多处登录并在非常接近的时间内几乎同时发单
# 考虑到VtTrader的应用场景认为以上情况不会构成问题
order.vtOrderID = '.'.join([self.gatewayName, order.orderID])
# 推送
self.gateway.onOrder(order)
@ -829,11 +931,12 @@ class CtpTdApi(TdApi):
"""成交回报"""
# 创建报单数据对象
trade = VtTradeData()
order.gatewayName = self.gatewayName
trade.gatewayName = self.gatewayName
# 保存代码和报单号
trade.symbol = data['InstrumentID']
trade.vtSymbol = '.'.join([self.gatewayName, trade.symbol])
trade.exchange = exchangeMapReverse[data['ExchangeID']]
trade.vtSymbol = trade.symbol #'.'.join([trade.symbol, trade.exchange])
trade.tradeID = data['TradeID']
trade.vtTradeID = '.'.join([self.gatewayName, trade.tradeID])
@ -842,20 +945,10 @@ class CtpTdApi(TdApi):
trade.vtOrderID = '.'.join([self.gatewayName, trade.orderID])
# 方向
if data['Direction'] == '0':
trade.direction = DIRECTION_LONG
elif data['Direction'] == '1':
trade.direction = DIRECTION_SHORT
else:
trade.direction = DIRECTION_UNKNOWN
trade.direction = directionMapReverse.get(data['Direction'], '')
# 开平
if data['OffsetFlag'] == '0':
trade.offset = OFFSET_OPEN
elif data['OffsetFlag'] == '1':
trade.offset = OFFSET_CLOSE
else:
trade.offset = OFFSET_UNKNOW
trade.offset = offsetMapReverse.get(data['OffsetFlag'], '')
# 价格、报单量等数值
trade.price = data['Price']
@ -1146,9 +1239,9 @@ class CtpTdApi(TdApi):
# 下面如果由于传入的类型本接口不支持,则会返回空字符串
try:
req['OrderPriceType'] = self.priceTypeMap[orderReq.priceType]
req['Direction'] = self.directionMap[orderReq.priceType]
req['CombOffsetFlag'] = self.offsetMap[orderReq.offset]
req['OrderPriceType'] = priceTypeMap[orderReq.priceType]
req['Direction'] = directionMap[orderReq.direction]
req['CombOffsetFlag'] = offsetMap[orderReq.offset]
except KeyError:
return ''
@ -1168,7 +1261,8 @@ class CtpTdApi(TdApi):
self.reqOrderInsert(req, self.reqID)
# 返回订单号(字符串),便于某些算法进行动态管理
return str(self.orderRef)
vtOrderID = '.'.join([self.gatewayName, str(self.orderRef)])
return vtOrderID
#----------------------------------------------------------------------
def cancelOrder(self, cancelOrderReq):
@ -1179,7 +1273,7 @@ class CtpTdApi(TdApi):
req['InstrumentID'] = cancelOrderReq.symbol
req['ExchangeID'] = cancelOrderReq.exchange
req['OrderRef'] = cancelOrderReq.orderRef
req['OrderRef'] = cancelOrderReq.orderID
req['FrontID'] = cancelOrderReq.frontID
req['SessionID'] = cancelOrderReq.sessionID
@ -1193,3 +1287,29 @@ class CtpTdApi(TdApi):
def close(self):
"""关闭"""
self.exit()
#----------------------------------------------------------------------
def test():
"""测试"""
from PyQt4 import QtCore
import sys
def print_log(event):
log = event.dict_['data']
print ':'.join([log.logTime, log.logContent])
app = QtCore.QCoreApplication(sys.argv)
eventEngine = EventEngine()
eventEngine.register(EVENT_LOG, print_log)
eventEngine.start()
gateway = CtpGateway(eventEngine)
gateway.connect()
sys.exit(app.exec_())
if __name__ == '__main__':
test()

View File

@ -143,7 +143,7 @@ class EventEngine:
"""注销事件处理函数监听"""
# 尝试获取该事件类型对应的处理函数列表,若无则忽略该次注销请求
try:
handlerList = self.handlers[type_]
handlerList = self.__handlers[type_]
# 如果该函数存在于列表中,则移除
if handler in handlerList:
@ -151,7 +151,7 @@ class EventEngine:
# 如果函数列表为空,则从引擎中移除该事件类型
if not handlerList:
del self.handlers[type_]
del self.__handlers[type_]
except KeyError:
pass

View File

@ -24,6 +24,12 @@ EVENT_ACCOUNT = 'eAccount.' # 账户回报事件
EVENT_CONTRACT = 'eContract.' # 合约基础信息回报事件
EVENT_ERROR = 'eError.' # 错误回报事件
# CTA模块相关
EVENT_CTA_LOG = 'eCtaLog' # CTA相关的日志事件
# Wind接口相关
EVENT_WIND_CONNECTREQ = 'eWindConnectReq' # Wind接口请求连接事件
#----------------------------------------------------------------------
def test():

2181
vn.trader/ltsDataType.py Normal file

File diff suppressed because it is too large Load Diff

1240
vn.trader/ltsGateway.py Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -5,22 +5,152 @@ from collections import OrderedDict
from PyQt4 import QtGui, QtCore
from eventEngine import *
from vtFunction import *
from vtGateway import *
BASIC_FONT = QtGui.QFont(u'微软雅黑', 12)
########################################################################
class BasicCell(QtGui.QTableWidgetItem):
"""基础的单元格"""
#----------------------------------------------------------------------
def __init__(self, text=None):
"""Constructor"""
super(BasicCell, self).__init__()
self.data = None
if text:
self.setContent(text)
#----------------------------------------------------------------------
def setContent(self, text):
"""设置内容"""
self.setText(text)
########################################################################
class DirectionCell(QtGui.QTableWidgetItem):
"""用来显示买卖方向的单元格"""
#----------------------------------------------------------------------
def __init__(self, text=None):
"""Constructor"""
super(DirectionCell, self).__init__()
self.data = None
if text:
self.setContent(text)
#----------------------------------------------------------------------
def setContent(self, text):
"""设置内容"""
if text == DIRECTION_LONG or text == DIRECTION_NET:
self.setForeground(QtGui.QColor('red'))
elif text == DIRECTION_SHORT:
self.setForeground(QtGui.QColor('green'))
self.setText(text)
########################################################################
class NameCell(QtGui.QTableWidgetItem):
"""用来显示合约中文的单元格"""
dataEngine = None
#----------------------------------------------------------------------
def __init__(self, text=None, dataEngine=None):
"""Constructor"""
super(NameCell, self).__init__()
self.data = None
if text:
self.setContent(text)
#----------------------------------------------------------------------
@staticmethod
def setDataEngine(self, dataEngine):
"""设置读取合约用的数据引擎对象"""
self.dataEngine = dataEngine
#----------------------------------------------------------------------
def setContent(self, text):
"""设置内容"""
if self.dataEngine:
# 首先尝试正常获取合约对象
contract = self.dataEngine.getContract(text)
# 如果交易所代码为UNKNOWN则删除交易所代码部分后再试
if EXCHANGE_UNKNOWN in text:
i = text.index('.')
symbol = text[:i]
contract = self.dataEngine.getContract(symbol)
# 如果能读取合约信息
if contract:
self.setText(contract.name)
########################################################################
class BidCell(QtGui.QTableWidgetItem):
"""买价单元格"""
#----------------------------------------------------------------------
def __init__(self, text=None):
"""Constructor"""
super(BidCell, self).__init__()
self.data = None
self.setBackground(QtGui.QColor(255,174,201))
if text:
self.setContent(text)
#----------------------------------------------------------------------
def setContent(self, text):
"""设置内容"""
self.setText(text)
########################################################################
class AskCell(QtGui.QTableWidgetItem):
"""买价单元格"""
#----------------------------------------------------------------------
def __init__(self, text=None):
"""Constructor"""
super(AskCell, self).__init__()
self.data = None
self.setBackground(QtGui.QColor(160,255,160))
if text:
self.setContent(text)
#----------------------------------------------------------------------
def setContent(self, text):
"""设置内容"""
self.setText(text)
########################################################################
class BasicMonitor(QtGui.QTableWidget):
"""基础监控"""
"""
基础监控
headerDict中的值对应的字典格式如下
{'chinese': u'中文名', 'cellType': BasicCell}
"""
signal = QtCore.pyqtSignal(type(Event()))
#----------------------------------------------------------------------
def __init__(self, eventEngine, parent=None):
def __init__(self, eventEngine=None, parent=None):
"""Constructor"""
super(BasicMonitor, self).__init__(parent)
self.eventEngine = eventEngine
# 保存表头标签用
self.headerDict = OrderedDict() # 有序字典key是英文名value是对应的中文名
self.headerDict = OrderedDict() # 有序字典key是英文名value是对应的配置字典
self.headerList = [] # 对应self.headerDict.keys()
# 保存相关数据用
@ -33,6 +163,9 @@ class BasicMonitor(QtGui.QTableWidget):
# 字体
self.font = None
# 保存数据对象到单元格
self.saveData = False
#----------------------------------------------------------------------
def setHeaderDict(self, headerDict):
"""设置表头有序字典"""
@ -54,15 +187,21 @@ class BasicMonitor(QtGui.QTableWidget):
"""设置字体"""
self.font = font
#----------------------------------------------------------------------
def setSaveData(self, saveData):
"""设置是否要保存数据到单元格"""
self.saveData = saveData
#----------------------------------------------------------------------
def initTable(self):
"""初始化表格"""
# 设置表格的列数
col = len(self.labelDict)
col = len(self.headerDict)
self.setColumnCount(col)
# 设置列表头
self.setHorizontalHeaderLabels(self.labelDict.values())
labels = [d['chinese'] for d in self.headerDict.values()]
self.setHorizontalHeaderLabels(labels)
# 关闭左边的垂直表头
self.verticalHeader().setVisible(False)
@ -70,29 +209,42 @@ class BasicMonitor(QtGui.QTableWidget):
# 设为不可编辑
self.setEditTriggers(self.NoEditTriggers)
# 设为行交替颜色
self.setAlternatingRowColors(True)
#----------------------------------------------------------------------
def registerEvent(self):
"""注册GUI更新相关的事件监听"""
self.signal.connect(self.updateData)
self.signal.connect(self.updateEvent)
self.eventEngine.register(self.eventType, self.signal.emit)
#----------------------------------------------------------------------
def updateData(self, event):
"""更新数据"""
def updateEvent(self, event):
"""收到事件更新"""
data = event.dict_['data']
key = data.__getattribute__(self.dataKey)
self.updateData(data)
#----------------------------------------------------------------------
def updateData(self, data):
"""将数据更新到表格中"""
# 如果设置了dataKey则采用存量更新模式
if self.dataKey:
key = data.__getattribute__(self.dataKey)
# 如果键在数据字典中不存在,则先插入新的一行,并创建对应单元格
if key not in self.dataDict:
self.insertRow(0)
d = {}
for n, header in enumerate(self.headerList):
content = unicode(data.__getattribute__(header))
cell = QtGui.QTableWidgetItem(content)
content = safeUnicode(data.__getattribute__(header))
cellType = self.headerDict[header]['cellType']
cell = cellType(content)
if self.font:
cell.setFont(font) # 如果设置了特殊字体,则进行单元格设置
cell.setFont(self.font) # 如果设置了特殊字体,则进行单元格设置
if self.saveData: # 如果设置了保存数据对象,则进行对象保存
cell.data = data
self.setItem(0, n, cell)
d[header] = cell
self.dataDict[key] = d
@ -100,17 +252,26 @@ class BasicMonitor(QtGui.QTableWidget):
else:
d = self.dataDict[key]
for header in self.headerList:
content = unicode(data.__getattribute__(header))
content = safeUnicode(data.__getattribute__(header))
cell = d[header]
cell.setText(content)
cell.setContent(content)
if self.saveData: # 如果设置了保存数据对象,则进行对象保存
cell.data = data
# 否则采用增量更新模式
else:
self.insertRow(0)
for n, header in enumerate(self.headerList):
content = unicode(data.__getattribute__(header))
cell = QtGui.QTableWidgetItem(content)
content = safeUnicode(data.__getattribute__(header))
cellType = self.headerDict[header]['cellType']
cell = cellType(content)
if self.font:
cell.setFont(font)
cell.setFont(self.font)
if self.saveData:
cell.data = data
self.setItem(0, n, cell)
# 调整列宽
@ -133,19 +294,20 @@ class MarketMonitor(BasicMonitor):
# 设置表头有序字典
d = OrderedDict()
d['symbol'] = u'合约代码'
d['vtSymbol'] = u'系统代码'
d['lastPrice'] = u'最新价'
d['volume'] = u'成交量'
d['openInterest'] = u'持仓量'
d['openPrice'] = u'开盘价'
d['highPrice'] = u'最高价'
d['lowPrice'] = u'最低价'
d['bidPrice1'] = u'买一价'
d['bidVolume1'] = u'买一量'
d['askPrice1'] = u'卖一价'
d['askVolume1'] = u'卖一量'
d['tickTime'] = u'时间'
d['symbol'] = {'chinese':u'合约代码', 'cellType':BasicCell}
d['vtSymbol'] = {'chinese':u'名称', 'cellType':NameCell}
d['lastPrice'] = {'chinese':u'最新价', 'cellType':BasicCell}
d['volume'] = {'chinese':u'成交量', 'cellType':BasicCell}
d['openInterest'] = {'chinese':u'持仓量', 'cellType':BasicCell}
d['openPrice'] = {'chinese':u'开盘价', 'cellType':BasicCell}
d['highPrice'] = {'chinese':u'最高价', 'cellType':BasicCell}
d['lowPrice'] = {'chinese':u'最低价', 'cellType':BasicCell}
d['bidPrice1'] = {'chinese':u'买一价', 'cellType':BidCell}
d['bidVolume1'] = {'chinese':u'买一量', 'cellType':BidCell}
d['askPrice1'] = {'chinese':u'卖一价', 'cellType':AskCell}
d['askVolume1'] = {'chinese':u'卖一量', 'cellType':AskCell}
d['time'] = {'chinese':u'时间', 'cellType':BasicCell}
d['gatewayName'] = {'chinese':u'接口', 'cellType':BasicCell}
self.setHeaderDict(d)
# 设置数据键
@ -154,6 +316,9 @@ class MarketMonitor(BasicMonitor):
# 设置监控事件类型
self.setEventType(EVENT_TICK)
# 设置字体
self.setFont(BASIC_FONT)
# 初始化表格
self.initTable()
@ -171,12 +336,13 @@ class LogMonitor(BasicMonitor):
super(LogMonitor, self).__init__(eventEngine, parent)
d = OrderedDict()
d['gatewayName'] = u'接口'
d['logTime'] = u'时间'
d['logContent'] = u'内容'
d['logTime'] = {'chinese':u'时间', 'cellType':BasicCell}
d['logContent'] = {'chinese':u'内容', 'cellType':BasicCell}
d['gatewayName'] = {'chinese':u'接口', 'cellType':BasicCell}
self.setHeaderDict(d)
self.setEventType(EVENT_LOG)
self.setFont(BASIC_FONT)
self.initTable()
self.registerEvent()
@ -191,12 +357,14 @@ class ErrorMonitor(BasicMonitor):
super(ErrorMonitor, self).__init__(eventEngine, parent)
d = OrderedDict()
d['gatewayName'] = u'接口'
d['errorID'] = u'错误代码'
d['errorMsg'] = u'错误信息'
d['errorID'] = {'chinese':u'错误代码', 'cellType':BasicCell}
d['errorMsg'] = {'chinese':u'错误信息', 'cellType':BasicCell}
d['additionalInfo'] = {'chinese':u'补充信息', 'cellType':BasicCell}
d['gatewayName'] = {'chinese':u'接口', 'cellType':BasicCell}
self.setHeaderDict(d)
self.setEventType(EVENT_ERROR)
self.setFont(BASIC_FONT)
self.initTable()
self.registerEvent()
@ -211,18 +379,20 @@ class TradeMonitor(BasicMonitor):
super(TradeMonitor, self).__init__(eventEngine, parent)
d = OrderedDict()
d['gatewayName'] = u'接口'
d['tradeID'] = u'成交编号'
d['orderID'] = u'委托编号'
d['symbol'] = u'合约代码'
d['direction'] = u'方向'
d['offset'] = u'开平'
d['price'] = u'价格'
d['volume'] = u'数量'
d['tradeTime'] = u'成交时间'
d['tradeID'] = {'chinese':u'成交编号', 'cellType':BasicCell}
d['orderID'] = {'chinese':u'委托编号', 'cellType':BasicCell}
d['symbol'] = {'chinese':u'合约代码', 'cellType':BasicCell}
d['vtSymbol'] = {'chinese':u'名称', 'cellType':NameCell}
d['direction'] = {'chinese':u'方向', 'cellType':DirectionCell}
d['offset'] = {'chinese':u'开平', 'cellType':BasicCell}
d['price'] = {'chinese':u'价格', 'cellType':BasicCell}
d['volume'] = {'chinese':u'数量', 'cellType':BasicCell}
d['tradeTime'] = {'chinese':u'成交时间', 'cellType':BasicCell}
d['gatewayName'] = {'chinese':u'接口', 'cellType':BasicCell}
self.setHeaderDict(d)
self.setEventType(EVENT_TRADE)
self.setFont(BASIC_FONT)
self.initTable()
self.registerEvent()
@ -232,31 +402,58 @@ class OrderMonitor(BasicMonitor):
"""委托监控"""
#----------------------------------------------------------------------
def __init__(self, eventEngine, parent=None):
def __init__(self, eventEngine, mainEngine, parent=None):
"""Constructor"""
super(OrderMonitor, self).__init__(eventEngine, parent)
self.mainEngine = mainEngine
d = OrderedDict()
d['gatewayName'] = u'接口'
d['orderID'] = u'委托编号'
d['symbol'] = u'合约代码'
d['direction'] = u'方向'
d['offset'] = u'开平'
d['price'] = u'价格'
d['totalVolume'] = u'委托数量'
d['tradedVolume'] = u'成交数量'
d['status'] = u'状态'
d['orderTime'] = u'委托时间'
d['tradeTime'] = u'撤销时间'
d['frontID'] = u'前置编号'
d['sessionID'] = u'会话编号'
d['orderID'] = {'chinese':u'委托编号', 'cellType':BasicCell}
d['symbol'] = {'chinese':u'合约代码', 'cellType':BasicCell}
d['vtSymbol'] = {'chinese':u'名称', 'cellType':NameCell}
d['direction'] = {'chinese':u'方向', 'cellType':DirectionCell}
d['offset'] = {'chinese':u'开平', 'cellType':BasicCell}
d['price'] = {'chinese':u'价格', 'cellType':BasicCell}
d['totalVolume'] = {'chinese':u'委托数量', 'cellType':BasicCell}
d['tradedVolume'] = {'chinese':u'成交数量', 'cellType':BasicCell}
d['status'] = {'chinese':u'状态', 'cellType':BasicCell}
d['orderTime'] = {'chinese':u'委托时间', 'cellType':BasicCell}
d['cancelTime'] = {'chinese':u'撤销时间', 'cellType':BasicCell}
d['frontID'] = {'chinese':u'前置编号', 'cellType':BasicCell}
d['sessionID'] = {'chinese':u'会话编号', 'cellType':BasicCell}
d['gatewayName'] = {'chinese':u'接口', 'cellType':BasicCell}
self.setHeaderDict(d)
self.setDataKey('vtOrderID')
self.setEventType(EVENT_ORDER)
self.setFont(BASIC_FONT)
self.setSaveData(True)
self.initTable()
self.registerEvent()
self.connectSignal()
#----------------------------------------------------------------------
def connectSignal(self):
"""连接信号"""
# 双击单元格撤单
self.itemDoubleClicked.connect(self.cancelOrder)
#----------------------------------------------------------------------
def cancelOrder(self, cell):
"""根据单元格的数据撤单"""
order = cell.data
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)
########################################################################
class PositionMonitor(BasicMonitor):
@ -268,16 +465,18 @@ class PositionMonitor(BasicMonitor):
super(PositionMonitor, self).__init__(eventEngine, parent)
d = OrderedDict()
d['gatewayName'] = u'接口'
d['symbol'] = u'合约代码'
d['direction'] = u'方向'
d['position'] = u'持仓量'
d['frozen'] = u'冻结量'
d['price'] = u'价格'
d['symbol'] = {'chinese':u'合约代码', 'cellType':BasicCell}
d['vtSymbol'] = {'chinese':u'名称', 'cellType':NameCell}
d['direction'] = {'chinese':u'方向', 'cellType':DirectionCell}
d['position'] = {'chinese':u'持仓量', 'cellType':BasicCell}
d['frozen'] = {'chinese':u'冻结量', 'cellType':BasicCell}
d['price'] = {'chinese':u'价格', 'cellType':BasicCell}
d['gatewayName'] = {'chinese':u'接口', 'cellType':BasicCell}
self.setHeaderDict(d)
self.setDataKey('vtPositionName')
self.setEventType(EVENT_POSITION)
self.setFont(BASIC_FONT)
self.initTable()
self.registerEvent()
@ -292,20 +491,451 @@ class AccountMonitor(BasicMonitor):
super(AccountMonitor, self).__init__(eventEngine, parent)
d = OrderedDict()
d['gatewayName'] = u'接口'
d['accountID'] = u'账户'
d['preBalance'] = u'昨结'
d['balance'] = u'净值'
d['available'] = u'可用'
d['commission'] = u'手续费'
d['margin'] = u'保证金'
d['closeProfit'] = u'平仓盈亏'
d['positionProfit'] = u'持仓盈亏'
d['accountID'] = {'chinese':u'账户', 'cellType':BasicCell}
d['preBalance'] = {'chinese':u'昨结', 'cellType':BasicCell}
d['balance'] = {'chinese':u'净值', 'cellType':BasicCell}
d['available'] = {'chinese':u'可用', 'cellType':BasicCell}
d['commission'] = {'chinese':u'手续费', 'cellType':BasicCell}
d['margin'] = {'chinese':u'保证金', 'cellType':BasicCell}
d['closeProfit'] = {'chinese':u'平仓盈亏', 'cellType':BasicCell}
d['positionProfit'] = {'chinese':u'持仓盈亏', 'cellType':BasicCell}
d['gatewayName'] = {'chinese':u'接口', 'cellType':BasicCell}
self.setHeaderDict(d)
self.setDataKey('vtAccountID')
self.setEventType(EVENT_ACCOUNT)
self.setFont(BASIC_FONT)
self.initTable()
self.registerEvent()
########################################################################
class TradingWidget(QtGui.QFrame):
"""简单交易组件"""
signal = QtCore.pyqtSignal(type(Event()))
directionList = [DIRECTION_LONG,
DIRECTION_SHORT]
offsetList = [OFFSET_OPEN,
OFFSET_CLOSE,
OFFSET_CLOSESYESTERDAY,
OFFSET_CLOSETODAY]
priceTypeList = [PRICETYPE_LIMITPRICE,
PRICETYPE_MARKETPRICE,
PRICETYPE_FAK,
PRICETYPE_FOK]
exchangeList = [EXCHANGE_NONE,
EXCHANGE_CFFEX,
EXCHANGE_SHFE,
EXCHANGE_DCE,
EXCHANGE_CZCE,
EXCHANGE_SSE,
EXCHANGE_SZSE]
#----------------------------------------------------------------------
def __init__(self, mainEngine, eventEngine, dataEngine, parent=None):
"""Constructor"""
super(TradingWidget, self).__init__(parent)
self.mainEngine = mainEngine
self.eventEngine = eventEngine
self.dataEngine = dataEngine
self.symbol = ''
self.initUi()
self.connectSignal()
#----------------------------------------------------------------------
def initUi(self):
"""初始化界面"""
self.setWindowTitle(u'交易')
self.setMaximumWidth(400)
self.setFrameShape(self.Box) # 设置边框
self.setLineWidth(1)
# 左边部分
labelSymbol = QtGui.QLabel(u'代码')
labelName = QtGui.QLabel(u'名称')
labelDirection = QtGui.QLabel(u'方向类型')
labelOffset = QtGui.QLabel(u'开平')
labelPrice = QtGui.QLabel(u'价格')
labelVolume = QtGui.QLabel(u'数量')
labelPriceType = QtGui.QLabel(u'价格类型')
labelExchange = QtGui.QLabel(u'交易所')
self.lineSymbol = QtGui.QLineEdit()
self.lineName = QtGui.QLineEdit()
self.comboDirection = QtGui.QComboBox()
self.comboDirection.addItems(self.directionList)
self.comboOffset = QtGui.QComboBox()
self.comboOffset.addItems(self.offsetList)
self.spinPrice = QtGui.QDoubleSpinBox()
self.spinPrice.setDecimals(4)
self.spinPrice.setMinimum(0)
self.spinPrice.setMaximum(100000)
self.spinVolume = QtGui.QSpinBox()
self.spinVolume.setMinimum(0)
self.spinVolume.setMaximum(1000000)
self.comboPriceType = QtGui.QComboBox()
self.comboPriceType.addItems(self.priceTypeList)
self.comboExchange = QtGui.QComboBox()
self.comboExchange.addItems(self.exchangeList)
gridleft = QtGui.QGridLayout()
gridleft.addWidget(labelSymbol, 0, 0)
gridleft.addWidget(labelName, 1, 0)
gridleft.addWidget(labelDirection, 2, 0)
gridleft.addWidget(labelOffset, 3, 0)
gridleft.addWidget(labelPrice, 4, 0)
gridleft.addWidget(labelVolume, 5, 0)
gridleft.addWidget(labelPriceType, 6, 0)
gridleft.addWidget(labelExchange, 7, 0)
gridleft.addWidget(self.lineSymbol, 0, 1)
gridleft.addWidget(self.lineName, 1, 1)
gridleft.addWidget(self.comboDirection, 2, 1)
gridleft.addWidget(self.comboOffset, 3, 1)
gridleft.addWidget(self.spinPrice, 4, 1)
gridleft.addWidget(self.spinVolume, 5, 1)
gridleft.addWidget(self.comboPriceType, 6, 1)
gridleft.addWidget(self.comboExchange, 7, 1)
# 右边部分
labelBid1 = QtGui.QLabel(u'买一')
labelBid2 = QtGui.QLabel(u'买二')
labelBid3 = QtGui.QLabel(u'买三')
labelBid4 = QtGui.QLabel(u'买四')
labelBid5 = QtGui.QLabel(u'买五')
labelAsk1 = QtGui.QLabel(u'卖一')
labelAsk2 = QtGui.QLabel(u'卖二')
labelAsk3 = QtGui.QLabel(u'卖三')
labelAsk4 = QtGui.QLabel(u'卖四')
labelAsk5 = QtGui.QLabel(u'卖五')
self.labelBidPrice1 = QtGui.QLabel()
self.labelBidPrice2 = QtGui.QLabel()
self.labelBidPrice3 = QtGui.QLabel()
self.labelBidPrice4 = QtGui.QLabel()
self.labelBidPrice5 = QtGui.QLabel()
self.labelBidVolume1 = QtGui.QLabel()
self.labelBidVolume2 = QtGui.QLabel()
self.labelBidVolume3 = QtGui.QLabel()
self.labelBidVolume4 = QtGui.QLabel()
self.labelBidVolume5 = QtGui.QLabel()
self.labelAskPrice1 = QtGui.QLabel()
self.labelAskPrice2 = QtGui.QLabel()
self.labelAskPrice3 = QtGui.QLabel()
self.labelAskPrice4 = QtGui.QLabel()
self.labelAskPrice5 = QtGui.QLabel()
self.labelAskVolume1 = QtGui.QLabel()
self.labelAskVolume2 = QtGui.QLabel()
self.labelAskVolume3 = QtGui.QLabel()
self.labelAskVolume4 = QtGui.QLabel()
self.labelAskVolume5 = QtGui.QLabel()
labelLast = QtGui.QLabel(u'最新')
self.labelLastPrice = QtGui.QLabel()
self.labelReturn = QtGui.QLabel()
self.labelLastPrice.setMinimumWidth(60)
self.labelReturn.setMinimumWidth(60)
gridRight = QtGui.QGridLayout()
gridRight.addWidget(labelAsk5, 0, 0)
gridRight.addWidget(labelAsk4, 1, 0)
gridRight.addWidget(labelAsk3, 2, 0)
gridRight.addWidget(labelAsk2, 3, 0)
gridRight.addWidget(labelAsk1, 4, 0)
gridRight.addWidget(labelLast, 5, 0)
gridRight.addWidget(labelBid1, 6, 0)
gridRight.addWidget(labelBid2, 7, 0)
gridRight.addWidget(labelBid3, 8, 0)
gridRight.addWidget(labelBid4, 9, 0)
gridRight.addWidget(labelBid5, 10, 0)
gridRight.addWidget(self.labelAskPrice5, 0, 1)
gridRight.addWidget(self.labelAskPrice4, 1, 1)
gridRight.addWidget(self.labelAskPrice3, 2, 1)
gridRight.addWidget(self.labelAskPrice2, 3, 1)
gridRight.addWidget(self.labelAskPrice1, 4, 1)
gridRight.addWidget(self.labelLastPrice, 5, 1)
gridRight.addWidget(self.labelBidPrice1, 6, 1)
gridRight.addWidget(self.labelBidPrice2, 7, 1)
gridRight.addWidget(self.labelBidPrice3, 8, 1)
gridRight.addWidget(self.labelBidPrice4, 9, 1)
gridRight.addWidget(self.labelBidPrice5, 10, 1)
gridRight.addWidget(self.labelAskVolume5, 0, 2)
gridRight.addWidget(self.labelAskVolume4, 1, 2)
gridRight.addWidget(self.labelAskVolume3, 2, 2)
gridRight.addWidget(self.labelAskVolume2, 3, 2)
gridRight.addWidget(self.labelAskVolume1, 4, 2)
gridRight.addWidget(self.labelReturn, 5, 2)
gridRight.addWidget(self.labelBidVolume1, 6, 2)
gridRight.addWidget(self.labelBidVolume2, 7, 2)
gridRight.addWidget(self.labelBidVolume3, 8, 2)
gridRight.addWidget(self.labelBidVolume4, 9, 2)
gridRight.addWidget(self.labelBidVolume5, 10, 2)
# 发单按钮
buttonSendOrder = QtGui.QPushButton(u'发单')
buttonCancelAll = QtGui.QPushButton(u'全撤')
size = buttonSendOrder.sizeHint()
buttonSendOrder.setMinimumHeight(size.height()*2) # 把按钮高度设为默认两倍
buttonCancelAll.setMinimumHeight(size.height()*2)
# 整合布局
hbox = QtGui.QHBoxLayout()
hbox.addLayout(gridleft)
hbox.addLayout(gridRight)
vbox = QtGui.QVBoxLayout()
vbox.addLayout(hbox)
vbox.addWidget(buttonSendOrder)
vbox.addWidget(buttonCancelAll)
vbox.addStretch()
self.setLayout(vbox)
# 关联更新
buttonSendOrder.clicked.connect(self.sendOrder)
buttonCancelAll.clicked.connect(self.cancelAll)
self.lineSymbol.returnPressed.connect(self.updateSymbol)
#----------------------------------------------------------------------
def updateSymbol(self):
"""合约变化"""
symbol = unicode(self.lineSymbol.text())
exchange = unicode(self.comboExchange.currentText())
if exchange:
vtSymbol = '.'.join([symbol, exchange])
contract = self.dataEngine.getContract(vtSymbol)
else:
contract = self.dataEngine.getContract(symbol)
if contract:
exchange = contract.exchange # 保证有交易所代码
self.lineName.setText(contract.name)#.decode('GBK'))
# 清空价格数量
self.spinPrice.setValue(0)
self.spinVolume.setValue(0)
# 清空行情显示
self.labelBidPrice1.setText('')
self.labelBidPrice2.setText('')
self.labelBidPrice3.setText('')
self.labelBidPrice4.setText('')
self.labelBidPrice5.setText('')
self.labelBidVolume1.setText('')
self.labelBidVolume2.setText('')
self.labelBidVolume3.setText('')
self.labelBidVolume4.setText('')
self.labelBidVolume5.setText('')
self.labelAskPrice1.setText('')
self.labelAskPrice2.setText('')
self.labelAskPrice3.setText('')
self.labelAskPrice4.setText('')
self.labelAskPrice5.setText('')
self.labelAskVolume1.setText('')
self.labelAskVolume2.setText('')
self.labelAskVolume3.setText('')
self.labelAskVolume4.setText('')
self.labelAskVolume5.setText('')
self.labelLastPrice.setText('')
self.labelReturn.setText('')
# 重新注册事件监听
self.eventEngine.unregister(EVENT_TICK + self.symbol, self.signal.emit)
self.eventEngine.register(EVENT_TICK + contract.vtSymbol, self.signal.emit)
# 订阅合约
req = VtSubscribeReq()
req.symbol = symbol
req.exchange = exchange
self.mainEngine.subscribe(req, contract.gatewayName)
# 更新组件当前交易的合约
self.symbol = contract.vtSymbol
#----------------------------------------------------------------------
def updateTick(self, event):
"""更新行情"""
tick = event.dict_['data']
if tick.vtSymbol == self.symbol:
self.labelBidPrice1.setText(str(tick.bidPrice1))
self.labelAskPrice1.setText(str(tick.askPrice1))
self.labelBidVolume1.setText(str(tick.bidVolume1))
self.labelAskVolume1.setText(str(tick.askVolume1))
if tick.bidPrice2:
self.labelBidPrice2.setText(str(tick.bidPrice2))
self.labelBidPrice3.setText(str(tick.bidPrice3))
self.labelBidPrice4.setText(str(tick.bidPrice4))
self.labelBidPrice5.setText(str(tick.bidPrice5))
self.labelAskPrice2.setText(str(tick.askPrice2))
self.labelAskPrice3.setText(str(tick.askPrice3))
self.labelAskPrice4.setText(str(tick.askPrice4))
self.labelAskPrice5.setText(str(tick.askPrice5))
self.labelBidVolume2.setText(str(tick.bidVolume2))
self.labelBidVolume3.setText(str(tick.bidVolume3))
self.labelBidVolume4.setText(str(tick.bidVolume4))
self.labelBidVolume5.setText(str(tick.bidVolume5))
self.labelAskVolume2.setText(str(tick.askVolume2))
self.labelAskVolume3.setText(str(tick.askVolume3))
self.labelAskVolume4.setText(str(tick.askVolume4))
self.labelAskVolume5.setText(str(tick.askVolume5))
self.labelLastPrice.setText(str(tick.lastPrice))
rt = (tick.lastPrice/tick.preClosePrice)-1
self.labelReturn.setText(('%.2f' %(rt*100))+'%')
#----------------------------------------------------------------------
def connectSignal(self):
"""连接Signal"""
self.signal.connect(self.updateTick)
#----------------------------------------------------------------------
def sendOrder(self):
"""发单"""
symbol = str(self.lineSymbol.text())
exchange = str(self.comboExchange.currentText())
if exchange:
vtSymbol = '.'.join([symbol, exchange])
contract = self.dataEngine.getContract(vtSymbol)
else:
contract = self.dataEngine.getContract(symbol)
if contract:
req = VtOrderReq()
req.symbol = symbol
req.exchange = contract.exchange
req.price = self.spinPrice.value()
req.volume = self.spinVolume.value()
req.direction = unicode(self.comboDirection.currentText())
req.priceType = unicode(self.comboPriceType.currentText())
req.offset = unicode(self.comboOffset.currentText())
self.mainEngine.sendOrder(req, contract.gatewayName)
#----------------------------------------------------------------------
def cancelAll(self):
"""一键撤销所有委托"""
l = self.dataEngine.getAllWorkingOrders()
for order in l:
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)
########################################################################
class ContractMonitor(BasicMonitor):
"""合约查询"""
#----------------------------------------------------------------------
def __init__(self, dataEngine, parent=None):
"""Constructor"""
super(ContractMonitor, self).__init__(parent=parent)
self.dataEngine = dataEngine
d = OrderedDict()
d['symbol'] = {'chinese':u'合约代码', 'cellType':BasicCell}
d['exchange'] = {'chinese':u'交易所', 'cellType':BasicCell}
d['vtSymbol'] = {'chinese':u'vt系统代码', 'cellType':BasicCell}
d['name'] = {'chinese':u'名称', 'cellType':BasicCell}
d['productClass'] = {'chinese':u'合约类型', 'cellType':BasicCell}
d['size'] = {'chinese':u'大小', 'cellType':BasicCell}
d['priceTick'] = {'chinese':u'最小价格变动', 'cellType':BasicCell}
#d['strikePrice'] = {'chinese':u'期权行权价', 'cellType':BasicCell}
#d['underlyingSymbol'] = {'chinese':u'期权标的物', 'cellType':BasicCell}
#d['optionType'] = {'chinese':u'期权类型', 'cellType':BasicCell}
self.setHeaderDict(d)
self.initUi()
#----------------------------------------------------------------------
def initUi(self):
"""初始化界面"""
self.setWindowTitle(u'合约查询')
self.setMinimumSize(800, 800)
self.setFont(BASIC_FONT)
self.initTable()
self.initMenu()
#----------------------------------------------------------------------
def showAllContracts(self):
"""显示所有合约数据"""
l = self.dataEngine.getAllContracts()
d = {'.'.join([contract.exchange, contract.symbol]):contract for contract in l}
l2 = d.keys()
l2.sort(reverse=True)
self.setRowCount(len(l2))
row = 0
for key in l2:
contract = d[key]
for n, header in enumerate(self.headerList):
content = safeUnicode(contract.__getattribute__(header))
cellType = self.headerDict[header]['cellType']
cell = cellType(content)
if self.font:
cell.setFont(self.font) # 如果设置了特殊字体,则进行单元格设置
self.setItem(row, n, cell)
row = row + 1
#----------------------------------------------------------------------
def refresh(self):
"""刷新"""
self.clearContents()
self.setRowCount(0)
self.showAllContracts()
self.menu.close() # 关闭菜单
#----------------------------------------------------------------------
def initMenu(self):
"""初始化右键菜单"""
refreshAction = QtGui.QAction(u'刷新', self)
refreshAction.triggered.connect(self.refresh)
self.menu = QtGui.QMenu(self)
self.menu.addAction(refreshAction)
#----------------------------------------------------------------------
def contextMenuEvent(self, event):
"""右键点击事件"""
self.menu.popup(QtGui.QCursor.pos())
#----------------------------------------------------------------------
def show(self):
"""显示"""
super(ContractMonitor, self).show()
self.refresh()

248
vn.trader/uiCtaWidget.py Normal file
View File

@ -0,0 +1,248 @@
# encoding: UTF-8
'''CTA模块相关的GUI控制组件'''
from uiBasicWidget import QtGui, QtCore, BasicCell
from eventEngine import *
########################################################################
class ValueMonitor(QtGui.QTableWidget):
"""数值监控"""
signal = QtCore.pyqtSignal()
#----------------------------------------------------------------------
def __init__(self, parent=None):
"""Constructor"""
super(ValueMonitor , self).__init__(parent)
self.keyCellDict = {}
self.row = 0
self.data = None
self.initUi()
self.signal.connect(self.updateTable)
#----------------------------------------------------------------------
def initUi(self):
"""初始化界面"""
self.setColumnCount(2)
self.verticalHeader().setVisible(False)
self.horizontalHeader().setVisible(False)
self.setEditTriggers(self.NoEditTriggers)
self.setAlternatingRowColors(True)
#----------------------------------------------------------------------
def updateData(self, data):
"""更新数据"""
self.data = data
self.signal.emit()
#----------------------------------------------------------------------
def updateTable(self):
"""更新表格"""
for key, value in self.data.items():
if key in self.keyCellDict:
cell = self.keyCellDict[key]
cell.setText(unicode(value))
else:
# 创建并保存单元格
keyCell = BasicCell(unicode(key))
cell = BasicCell(unicode(value))
self.keyCellDict[key] = cell
# 移动到下一行
self.insertRow(self.row)
self.setItem(self.row, 0, keyCell)
self.setItem(self.row, 1, cell)
self.row += 1
########################################################################
class CtaStrategyManager(QtGui.QGroupBox):
"""策略管理组件"""
#----------------------------------------------------------------------
def __init__(self, ctaEngine, eventEngine, name, parent=None):
"""Constructor"""
super(CtaStrategyManager, self).__init__(parent)
self.ctaEngine = ctaEngine
self.eventEngine = eventEngine
self.name = name
self.initUi()
self.updateMonitor()
self.registerEvent()
#----------------------------------------------------------------------
def initUi(self):
"""初始化界面"""
self.setTitle(self.name)
paramLabel = QtGui.QLabel(u'参数')
varLabel = QtGui.QLabel(u'变量')
self.paramMonitor = ValueMonitor(self)
self.varMonitor = ValueMonitor(self)
buttonStart = QtGui.QPushButton(u'启动')
buttonStop = QtGui.QPushButton(u'停止')
buttonStart.clicked.connect(self.start)
buttonStop.clicked.connect(self.stop)
hbox = QtGui.QHBoxLayout()
hbox.addWidget(buttonStart)
hbox.addWidget(buttonStop)
hbox.addStretch()
vbox = QtGui.QVBoxLayout()
vbox.addLayout(hbox)
vbox.addWidget(paramLabel)
vbox.addWidget(self.paramMonitor)
vbox.addWidget(varLabel)
vbox.addWidget(self.varMonitor)
self.setLayout(vbox)
#----------------------------------------------------------------------
def updateMonitor(self, event=None):
"""显示策略最新状态"""
paramDict = self.ctaEngine.getStrategyParam(self.name)
if paramDict:
self.paramMonitor.updateData(paramDict)
varDict = self.ctaEngine.getStrategyVar(self.name)
if varDict:
self.varMonitor.updateData(varDict)
#----------------------------------------------------------------------
def registerEvent(self):
"""注册事件监听"""
self.eventEngine.register(EVENT_TIMER, self.updateMonitor)
#----------------------------------------------------------------------
def start(self):
"""启动策略"""
self.ctaEngine.startStrategy(self.name)
#----------------------------------------------------------------------
def stop(self):
"""停止策略"""
self.ctaEngine.stopStrategy(self.name)
########################################################################
class CtaEngineManager(QtGui.QWidget):
"""CTA引擎管理组件"""
signal = QtCore.pyqtSignal(type(Event()))
#----------------------------------------------------------------------
def __init__(self, ctaEngine, eventEngine, parent=None):
"""Constructor"""
super(CtaEngineManager, self).__init__(parent)
self.ctaEngine = ctaEngine
self.eventEngine = eventEngine
self.strategyLoaded = False
self.initUi()
self.registerEvent()
# 记录日志
self.ctaEngine.writeCtaLog(u'CTA引擎启动成功')
#----------------------------------------------------------------------
def initUi(self):
"""初始化界面"""
self.setWindowTitle(u'CTA策略')
# 按钮
loadButton = QtGui.QPushButton(u'加载策略')
startAllButton = QtGui.QPushButton(u'全部启动')
stopAllButton = QtGui.QPushButton(u'全部停止')
loadButton.clicked.connect(self.load)
startAllButton.clicked.connect(self.startAll)
stopAllButton.clicked.connect(self.stopAll)
# 滚动区域放置所有的CtaStrategyManager
self.scrollArea = QtGui.QScrollArea()
# CTA组件的日志监控
self.ctaLogMonitor = QtGui.QTextEdit()
self.ctaLogMonitor.setReadOnly(True)
# 设置布局
hbox2 = QtGui.QHBoxLayout()
hbox2.addWidget(loadButton)
hbox2.addWidget(startAllButton)
hbox2.addWidget(stopAllButton)
hbox2.addStretch()
vbox = QtGui.QVBoxLayout()
vbox.addLayout(hbox2)
vbox.addWidget(self.scrollArea)
vbox.addWidget(self.ctaLogMonitor)
self.setLayout(vbox)
#----------------------------------------------------------------------
def initStrategyManager(self):
"""初始化策略管理组件界面"""
w = QtGui.QWidget()
hbox = QtGui.QHBoxLayout()
for name in self.ctaEngine.strategyDict.keys():
strategyManager = CtaStrategyManager(self.ctaEngine, self.eventEngine, name)
hbox.addWidget(strategyManager)
w.setLayout(hbox)
self.scrollArea.setWidget(w)
#----------------------------------------------------------------------
def startAll(self):
"""全部启动"""
for name in self.ctaEngine.strategyDict.keys():
self.ctaEngine.startStrategy(name)
#----------------------------------------------------------------------
def stopAll(self):
"""全部停止"""
for name in self.ctaEngine.strategyDict.keys():
self.ctaEngine.stopStrategy(name)
#----------------------------------------------------------------------
def load(self):
"""加载策略"""
if not self.strategyLoaded:
self.ctaEngine.loadStrategySetting()
self.initStrategyManager()
self.strategyLoaded = True
self.ctaEngine.writeCtaLog(u'策略加载成功')
#----------------------------------------------------------------------
def updateCtaLog(self, event):
"""更新CTA相关日志"""
log = event.dict_['data']
content = '\t'.join([log.logTime, log.logContent])
self.ctaLogMonitor.append(content)
#----------------------------------------------------------------------
def registerEvent(self):
"""注册事件监听"""
self.signal.connect(self.updateCtaLog)
self.eventEngine.register(EVENT_CTA_LOG, self.signal.emit)

289
vn.trader/uiMainWindow.py Normal file
View File

@ -0,0 +1,289 @@
# encoding: UTF-8
import psutil
from uiBasicWidget import *
from uiCtaWidget import CtaEngineManager
########################################################################
class MainWindow(QtGui.QMainWindow):
"""主窗口"""
#----------------------------------------------------------------------
def __init__(self, mainEngine, eventEngine, dataEngine):
"""Constructor"""
super(MainWindow, self).__init__()
self.mainEngine = mainEngine
self.eventEngine = eventEngine
self.dataEngine = dataEngine
self.initUi()
#----------------------------------------------------------------------
def initUi(self):
"""初始化界面"""
self.setWindowTitle('VnTrader')
self.initCentral()
self.initMenu()
self.initStatusBar()
#----------------------------------------------------------------------
def initCentral(self):
"""初始化中心区域"""
marketM = MarketMonitor(self.eventEngine)
logM = LogMonitor(self.eventEngine)
errorM = ErrorMonitor(self.eventEngine)
tradeM = TradeMonitor(self.eventEngine)
orderM = OrderMonitor(self.eventEngine, self.mainEngine)
positionM = PositionMonitor(self.eventEngine)
accountM = AccountMonitor(self.eventEngine)
tradingW = TradingWidget(self.mainEngine, self.mainEngine.eventEngine, self.mainEngine.dataEngine)
leftTab = QtGui.QTabWidget()
leftTab.addTab(logM, u'日志')
leftTab.addTab(errorM, u'错误')
leftTab.addTab(accountM, u'账户')
rightTab = QtGui.QTabWidget()
rightTab.addTab(tradeM, u'成交')
rightTab.addTab(orderM, u'委托')
rightTab.addTab(positionM, u'持仓')
hbox = QtGui.QHBoxLayout()
hbox.addWidget(tradingW)
hbox.addWidget(marketM)
grid = QtGui.QGridLayout()
grid.addLayout(hbox, 0, 0, 1, 2)
grid.addWidget(leftTab, 1, 0)
grid.addWidget(rightTab, 1, 1)
central = QtGui.QWidget()
central.setLayout(grid)
self.setCentralWidget(central)
#----------------------------------------------------------------------
def initMenu(self):
"""初始化菜单"""
# 创建操作
connectCtpAction = QtGui.QAction(u'连接CTP', self)
connectCtpAction.triggered.connect(self.connectCtp)
connectLtsAction = QtGui.QAction(u'连接LTS', self)
connectLtsAction.triggered.connect(self.connectLts)
connectWindAction = QtGui.QAction(u'连接Wind', self)
connectWindAction.triggered.connect(self.connectWind)
testAction = QtGui.QAction(u'测试', self)
testAction.triggered.connect(self.testSubscribe)
exitAction = QtGui.QAction(u'退出', self)
exitAction.triggered.connect(self.close)
aboutAction = QtGui.QAction(u'关于', self)
aboutAction.triggered.connect(self.openAbout)
contractAction = QtGui.QAction(u'查询合约', self)
contractAction.triggered.connect(self.openContract)
ctaAction = QtGui.QAction(u'CTA策略', self)
ctaAction.triggered.connect(self.openCta)
# 创建菜单
menubar = self.menuBar()
sysMenu = menubar.addMenu(u'系统')
sysMenu.addAction(connectCtpAction)
sysMenu.addAction(connectLtsAction)
sysMenu.addAction(connectWindAction)
sysMenu.addAction(testAction)
sysMenu.addAction(exitAction)
functionMenu = menubar.addMenu(u'功能')
functionMenu.addAction(contractAction)
functionMenu.addAction(ctaAction)
helpMenu = menubar.addMenu(u'帮助')
helpMenu.addAction(aboutAction)
#----------------------------------------------------------------------
def initStatusBar(self):
"""初始化状态栏"""
self.statusLabel = QtGui.QLabel()
self.statusLabel.setAlignment(QtCore.Qt.AlignLeft)
self.statusBar().addPermanentWidget(self.statusLabel)
self.statusLabel.setText(self.getCpuMemory())
self.sbCount = 0
self.sbTrigger = 10 # 10秒刷新一次
self.eventEngine.register(EVENT_TIMER, self.updateStatusBar)
#----------------------------------------------------------------------
def updateStatusBar(self, event):
"""在状态栏更新CPU和内存信息"""
self.sbCount += 1
if self.sbCount == self.sbTrigger:
self.sbCount = 0
self.statusLabel.setText(self.getCpuMemory())
#----------------------------------------------------------------------
def getCpuMemory(self):
"""获取CPU和内存状态信息"""
cpuPercent = psutil.cpu_percent()
memoryPercent = psutil.virtual_memory().percent
return u'CPU使用率%d%% 内存使用率:%d%%' % (cpuPercent, memoryPercent)
#----------------------------------------------------------------------
def connectCtp(self):
"""连接CTP接口"""
self.mainEngine.connect('CTP')
#----------------------------------------------------------------------
def connectLts(self):
"""连接LTS接口"""
self.mainEngine.connect('LTS')
#----------------------------------------------------------------------
def connectWind(self):
"""连接Wind接口"""
self.mainEngine.connect('Wind')
#----------------------------------------------------------------------
def testSubscribe(self):
"""测试订阅"""
req = VtSubscribeReq()
req.symbol = '600000'
req.exchange = EXCHANGE_SSE
self.mainEngine.subscribe(req, 'Wind')
req = VtSubscribeReq()
req.symbol = '000062'
req.exchange = EXCHANGE_SZSE
self.mainEngine.subscribe(req, 'Wind')
req = VtSubscribeReq()
req.symbol = 'IF1511'
req.exchange = EXCHANGE_CFFEX
self.mainEngine.subscribe(req, 'Wind')
req = VtSubscribeReq()
req.symbol = 'CU1601'
req.exchange = EXCHANGE_SHFE
self.mainEngine.subscribe(req, 'Wind')
req = VtSubscribeReq()
req.symbol = 'C1601'
req.exchange = EXCHANGE_DCE
self.mainEngine.subscribe(req, 'Wind')
req = VtSubscribeReq()
req.symbol = 'SR1601'
req.exchange = EXCHANGE_CZCE
self.mainEngine.subscribe(req, 'Wind')
#----------------------------------------------------------------------
def openAbout(self):
"""打开关于"""
try:
self.aboutW.show()
except AttributeError:
self.aboutW = AboutWidget(self)
self.aboutW.show()
#----------------------------------------------------------------------
def openContract(self):
"""打开合约查询"""
try:
self.contractM.show()
except AttributeError:
self.contractM = ContractMonitor(self.mainEngine.dataEngine)
self.contractM.show()
#----------------------------------------------------------------------
def openCta(self):
"""打开CTA组件"""
try:
self.ctaM.show()
except AttributeError:
self.ctaM = CtaEngineManager(self.mainEngine.ctaEngine, self.eventEngine)
self.ctaM.show()
#----------------------------------------------------------------------
def closeEvent(self, event):
"""关闭事件"""
reply = QtGui.QMessageBox.question(self, u'退出',
u'确认退出?', QtGui.QMessageBox.Yes |
QtGui.QMessageBox.No, QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
self.mainEngine.exit()
event.accept()
else:
event.ignore()
########################################################################
class AboutWidget(QtGui.QDialog):
"""显示关于信息"""
#----------------------------------------------------------------------
def __init__(self, parent=None):
"""Constructor"""
super(AboutWidget, self).__init__(parent)
self.initUi()
#----------------------------------------------------------------------
def initUi(self):
""""""
self.setWindowTitle(u'关于')
text = u"""
VnTrader
更新日期2015/9/29
作者用Python的交易员
LicenseMIT
主页vnpy.org
Githubgithub.com/vnpy/vnpy
QQ交流群262656087
开发环境
操作系统Windows 7 专业版 64
Python发行版Python 2.7.6 (Anaconda 1.9.2 Win-32)
CTPvn.ctp 2015/6/1
图形库PyQt4 4.11.3 Py2.7-x32
事件驱动引擎vn.event
开发环境WingIDE 5.0.6
"""
label = QtGui.QLabel()
label.setText(text)
label.setMinimumWidth(500)
vbox = QtGui.QVBoxLayout()
vbox.addWidget(label)
self.setLayout(vbox)

BIN
vn.trader/vnltsmd.pyd Normal file

Binary file not shown.

BIN
vn.trader/vnltsqry.pyd Normal file

Binary file not shown.

BIN
vn.trader/vnltstd.pyd Normal file

Binary file not shown.

BIN
vn.trader/vnpy.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

57
vn.trader/vtConstant.py Normal file
View File

@ -0,0 +1,57 @@
# encoding: UTF-8
# 默认空值
EMPTY_STRING = ''
EMPTY_UNICODE = u''
EMPTY_INT = 0
EMPTY_FLOAT = 0.0
# 方向常量
DIRECTION_NONE = u'无方向'
DIRECTION_LONG = u''
DIRECTION_SHORT = u''
DIRECTION_UNKNOWN = u'未知'
DIRECTION_NET = u''
# 开平常量
OFFSET_NONE = u'无开平'
OFFSET_OPEN = u'开仓'
OFFSET_CLOSE = u'平仓'
OFFSET_CLOSETODAY = u'平今'
OFFSET_CLOSESYESTERDAY = u'平昨'
OFFSET_UNKNOWN = u'未知'
# 状态常量
STATUS_NOTTRADED = u'未成交'
STATUS_PARTTRADED = u'部分成交'
STATUS_ALLTRADED = u'全部成交'
STATUS_CANCELLED = u'已撤销'
STATUS_UNKNOWN = u'未知'
# 合约类型常量
PRODUCT_EQUITY = u'股票'
PRODUCT_FUTURES = u'期货'
PRODUCT_OPTION = u'期权'
PRODUCT_INDEX = u'指数'
PRODUCT_COMBINATION = u'组合'
PRODUCT_UNKNOWN = u'未知'
# 价格类型常量
PRICETYPE_LIMITPRICE = u'限价'
PRICETYPE_MARKETPRICE = u'市价'
PRICETYPE_FAK = u'FAK'
PRICETYPE_FOK = u'FOK'
# 期权类型
OPTION_CALL = u'看涨期权'
OPTION_PUT = u'看跌期权'
# 交易所类型
EXCHANGE_SSE = u'SSE' # 上交所
EXCHANGE_SZSE = u'SZSE' # 深交所
EXCHANGE_CFFEX = u'CFFEX' # 中金所
EXCHANGE_SHFE = u'SHFE' # 上期所
EXCHANGE_CZCE = u'CZCE' # 郑商所
EXCHANGE_DCE = u'DCE' # 大商所
EXCHANGE_UNKNOWN = 'UNKNOWN'# 未知交易所
EXCHANGE_NONE = '' # 空交易所

View File

@ -1,8 +1,17 @@
# encoding: UTF-8
import shelve
from pymongo import MongoClient
from pymongo.errors import ConnectionFailure
from eventEngine import *
from ctpGateway import CtpGateway
from ltsGateway import LtsGateway
from windGateway import WindGateway
from vtGateway import *
import uiBasicWidget
from ctaEngine import CtaEngine
########################################################################
@ -16,11 +25,27 @@ class MainEngine(object):
self.eventEngine = EventEngine()
self.eventEngine.start()
# 创建数据引擎
self.dataEngine = DataEngine(self.eventEngine)
uiBasicWidget.NameCell.setDataEngine(uiBasicWidget.NameCell, self.dataEngine) # 将数据引擎对象传给NameCell
# 用来保存接口对象的字典
self.gatewayDict = {}
# 创建我们想要接入的接口对象
self.addGateway(CtpGateway, 'CTP')
self.gatewayDict['CTP'].setQryEnabled(True)
self.addGateway(LtsGateway, 'LTS')
self.gatewayDict['LTS'].setQryEnabled(True)
self.addGateway(WindGateway, 'Wind') # 没有Wind的请注释掉这一行
# MongoDB数据库相关
self.dbClient = None # MongoDB客户端对象
# CTA引擎
self.ctaEngine = CtaEngine(self, self.eventEngine, self.dataEngine)
#----------------------------------------------------------------------
def addGateway(self, gateway, gatewayName=None):
@ -30,45 +55,203 @@ class MainEngine(object):
#----------------------------------------------------------------------
def connect(self, gatewayName):
"""连接特定名称的接口"""
if gatewayName in self.gatewayDict:
gateway = self.gatewayDict[gatewayName]
gateway.connect()
else:
self.writeLog(u'接口不存在:%s' %gatewayName)
#----------------------------------------------------------------------
def subscribe(self, subscribeReq, gatewayName):
"""订阅特定接口的行情"""
if gatewayName in self.gatewayDict:
gateway = self.gatewayDict[gatewayName]
gateway.subscribe(subscribeReq)
else:
self.writeLog(u'接口不存在:%s' %gatewayName)
#----------------------------------------------------------------------
def sendOrder(self, orderReq, gatewayName):
"""对特定接口发单"""
if gatewayName in self.gatewayDict:
gateway = self.gatewayDict[gatewayName]
return gateway.sendOrder(orderReq)
else:
self.writeLog(u'接口不存在:%s' %gatewayName)
#----------------------------------------------------------------------
def cancelOrder(self, cancelOrderReq, gatewayName):
"""对特定接口撤单"""
if gatewayName in self.gatewayDict:
gateway = self.gatewayDict[gatewayName]
gateway.cancelOrder(cancelOrderReq)
else:
self.writeLog(u'接口不存在:%s' %gatewayName)
#----------------------------------------------------------------------
def getAccont(self, gatewayName):
"""查询特定接口的账户"""
if gatewayName in self.gatewayDict:
gateway = self.gatewayDict[gatewayName]
gateway.getAccount()
else:
self.writeLog(u'接口不存在:%s' %gatewayName)
#----------------------------------------------------------------------
def getPosition(self, gatewayName):
"""查询特定接口的持仓"""
if gatewayName in self.gatewayDict:
gateway = self.gatewayDict[gatewayName]
gateway.getPosition()
else:
self.writeLog(u'接口不存在:%s' %gatewayName)
#----------------------------------------------------------------------
def exit(self):
"""退出程序前调用,保证正常退出"""
# 停止事件引擎
self.eventEngine.stop()
# 安全关闭所有接口
for gateway in self.gatewayDict.values():
gateway.close()
# 停止事件引擎
self.eventEngine.stop()
# 保存数据引擎里的合约数据到硬盘
self.dataEngine.saveContracts()
#----------------------------------------------------------------------
def writeLog(self, content):
"""快速发出日志事件"""
log = VtLogData()
log.logContent = content
event = Event(type_=EVENT_LOG)
event.dict_['data'] = log
self.eventEngine.put(event)
#----------------------------------------------------------------------
def dbConnect(self):
"""连接MongoDB数据库"""
if not self.dbClient:
try:
self.dbClient = MongoClient()
self.writeLog(u'MongoDB连接成功')
except ConnectionFailure:
self.writeLog(u'MongoDB连接失败')
#----------------------------------------------------------------------
def dbInsert(self, dbName, collectionName, d):
"""向MongoDB中插入数据d是具体数据"""
if self.dbClient:
db = self.dbClient[dbName]
collection = db[collectionName]
collection.insert(d)
#----------------------------------------------------------------------
def dbQuery(self, dbName, collectionName, d):
"""从MongoDB中读取数据d是查询要求返回的是数据库查询的指针"""
if self.dbClient:
db = self.dbClient[dbName]
collection = db[collectionName]
cursor = collection.find(d)
return cursor
else:
return None
########################################################################
class DataEngine(object):
"""数据引擎"""
contractFileName = 'ContractData.vt'
#----------------------------------------------------------------------
def __init__(self, eventEngine):
"""Constructor"""
self.eventEngine = eventEngine
# 保存合约详细信息的字典
self.contractDict = {}
# 保存委托数据的字典
self.orderDict = {}
# 保存活动委托数据的字典(即可撤销)
self.workingOrderDict = {}
# 读取保存在硬盘的合约数据
self.loadContracts()
# 注册事件监听
self.registerEvent()
#----------------------------------------------------------------------
def updateContract(self, event):
"""更新合约数据"""
contract = event.dict_['data']
self.contractDict[contract.vtSymbol] = contract
self.contractDict[contract.symbol] = contract # 使用常规代码(不包括交易所)可能导致重复
#----------------------------------------------------------------------
def getContract(self, vtSymbol):
"""查询合约对象"""
try:
return self.contractDict[vtSymbol]
except KeyError:
return None
#----------------------------------------------------------------------
def getAllContracts(self):
"""查询所有合约对象(返回列表)"""
return self.contractDict.values()
#----------------------------------------------------------------------
def saveContracts(self):
"""保存所有合约对象到硬盘"""
f = shelve.open(self.contractFileName)
f['data'] = self.contractDict
f.close()
#----------------------------------------------------------------------
def loadContracts(self):
"""从硬盘读取合约对象"""
f = shelve.open(self.contractFileName)
if 'data' in f:
d = f['data']
for key, value in d.items():
self.contractDict[key] = value
f.close()
#----------------------------------------------------------------------
def updateOrder(self, event):
"""更新委托数据"""
order = event.dict_['data']
self.orderDict[order.vtOrderID] = order
# 如果订单的状态是全部成交或者撤销则需要从workingOrderDict中移除
if order.status == STATUS_ALLTRADED or order.status == STATUS_CANCELLED:
if order.vtOrderID in self.workingOrderDict:
del self.workingOrderDict[order.vtOrderID]
# 否则则更新字典中的数据
else:
self.workingOrderDict[order.vtOrderID] = order
#----------------------------------------------------------------------
def getOrder(self, vtOrderID):
"""查询委托"""
try:
return self.orderDict[vtOrderID]
except KeyError:
return None
#----------------------------------------------------------------------
def getAllWorkingOrders(self):
"""查询所有活动委托(返回列表)"""
return self.workingOrderDict.values()
#----------------------------------------------------------------------
def registerEvent(self):
"""注册事件监听"""
self.eventEngine.register(EVENT_CONTRACT, self.updateContract)
self.eventEngine.register(EVENT_ORDER, self.updateOrder)

26
vn.trader/vtFunction.py Normal file
View File

@ -0,0 +1,26 @@
# encoding: UTF-8
"""
包含一些开放中常用的函数
"""
import decimal
MAX_NUMBER = 1000000000
MAX_DECIMAL = 4
#----------------------------------------------------------------------
def safeUnicode(value):
"""检查接口数据潜在的错误,保证转化为的字符串正确"""
# 检查是数字接近0时会出现的浮点数上限
if type(value) is int or type(value) is float:
if value > MAX_NUMBER:
value = 0
# 检查防止小数点位过多
if type(value) is float:
d = decimal.Decimal(str(value))
if abs(d.as_tuple().exponent) > MAX_DECIMAL:
value = round(value, ndigits=MAX_DECIMAL)
return unicode(value)

View File

@ -4,48 +4,7 @@ import time
from eventEngine import *
# 默认空值
EMPTY_STRING = ''
EMPTY_UNICODE = u''
EMPTY_INT = 0
EMPTY_FLOAT = 0.0
# 方向常量
DIRECTION_NONE = u'无方向'
DIRECTION_LONG = u''
DIRECTION_SHORT = u''
DIRECTION_UNKNOWN = u'未知'
DIRECTION_NET = u''
# 开平常量
OFFSET_NONE = u'无开平'
OFFSET_OPEN = u'开仓'
OFFSET_CLOSE = u'平仓'
OFFSET_CLOSETODAY = u'平今'
OFFSET_CLOSESYESTERDAY = u'平昨'
OFFSET_UNKNOWN = u'未知'
# 状态常量
STATUS_NOTTRADED = u'未成交'
STATUS_PARTTRADED = u'部分成交'
STATUS_ALLTRADED = u'全部成交'
STATUS_CANCELLED = u'已撤销'
STATUS_UNKNOWN = u'未知'
# 合约类型常量
PRODUCT_EQUITY = u'股票'
PRODUCT_FUTURES = u'期货'
PRODUCT_OPTION = u'期权'
PRODUCT_INDEX = u'指数'
PRODUCT_COMBINATION = u'组合'
# 价格类型常量
PRICETYPE_LIMITPRICE = u'限价'
PRICETYPE_MARKETPRICE = u'市价'
# 期权类型
OPTION_CALL = u'看涨期权'
OPTION_PUT = u'看跌期权'
from vtConstant import *
########################################################################
@ -200,18 +159,24 @@ class VtTickData(VtBaseData):
# 代码相关
self.symbol = EMPTY_STRING # 合约代码
self.vtSymbol = EMPTY_STRING # 合约在vt系统中的唯一代码通常是 Gateway名.合约代码
self.exchange = EMPTY_STRING # 交易所代码
self.vtSymbol = EMPTY_STRING # 合约在vt系统中的唯一代码通常是 合约代码.交易所代码
# 成交数据
self.lastPrice = EMPTY_FLOAT # 最新成交价
self.volume = EMPTY_INT # 最新成交量
self.openInterest = EMPTY_INT # 持仓量
self.tickTime = EMPTY_STRING # 更新时间
self.time = EMPTY_STRING # 时间 11:20:56.5
self.date = EMPTY_STRING # 日期 20151009
# 常规行情
self.openPrice = EMPTY_FLOAT # 今日开盘价
self.highPrice = EMPTY_FLOAT # 今日最高价
self.lowPrice = EMPTY_FLOAT # 今日最低价
self.preClosePrice = EMPTY_FLOAT
self.upperLimit = EMPTY_FLOAT # 涨停价
self.lowerLimit = EMPTY_FLOAT # 跌停价
# 五档行情
self.bidPrice1 = EMPTY_FLOAT
@ -250,7 +215,8 @@ class VtTradeData(VtBaseData):
# 代码编号相关
self.symbol = EMPTY_STRING # 合约代码
self.vtSymbol = EMPTY_STRING # 合约在vt系统中的唯一代码通常是 Gateway名.合约代码
self.exchange = EMPTY_STRING # 交易所代码
self.vtSymbol = EMPTY_STRING # 合约在vt系统中的唯一代码通常是 合约代码.交易所代码
self.tradeID = EMPTY_STRING # 成交编号
self.vtTradeID = EMPTY_STRING # 成交在vt系统中的唯一编号通常是 Gateway名.成交编号
@ -277,7 +243,8 @@ class VtOrderData(VtBaseData):
# 代码编号相关
self.symbol = EMPTY_STRING # 合约代码
self.vtSymbol = EMPTY_STRING # 合约在vt系统中的唯一代码通常是 Gateway名.合约代码
self.exchange = EMPTY_STRING # 交易所代码
self.vtSymbol = EMPTY_STRING # 合约在vt系统中的唯一代码通常是 合约代码.交易所代码
self.orderID = EMPTY_STRING # 订单编号
self.vtOrderID = EMPTY_STRING # 订单在vt系统中的唯一编号通常是 Gateway名.订单编号
@ -309,7 +276,8 @@ class VtPositionData(VtBaseData):
# 代码编号相关
self.symbol = EMPTY_STRING # 合约代码
self.vtSymbol = EMPTY_STRING # 合约在vt系统中的唯一代码通常是 Gateway名.合约代码
self.exchange = EMPTY_STRING # 交易所代码
self.vtSymbol = EMPTY_STRING # 合约在vt系统中的唯一代码合约代码.交易所代码
# 持仓相关
self.direction = EMPTY_STRING # 持仓方向
@ -353,6 +321,7 @@ class VtErrorData(VtBaseData):
self.errorID = EMPTY_STRING # 错误代码
self.errorMsg = EMPTY_UNICODE # 错误信息
self.additionalInfo = EMPTY_UNICODE # 补充信息
########################################################################
@ -378,7 +347,8 @@ class VtContractData(VtBaseData):
super(VtBaseData, self).__init__()
self.symbol = EMPTY_STRING # 代码
self.vtSymbol = EMPTY_STRING # 合约在vt系统中的唯一代码通常是 Gateway名.合约代码
self.exchange = EMPTY_STRING # 交易所代码
self.vtSymbol = EMPTY_STRING # 合约在vt系统中的唯一代码通常是 合约代码.交易所代码
self.name = EMPTY_UNICODE # 合约中文名
self.productClass = EMPTY_UNICODE # 合约类型
@ -409,13 +379,14 @@ class VtOrderReq:
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
self.symbol = EMPTY_STRING
self.price = EMPTY_FLOAT
self.volume = EMPTY_INT
self.symbol = EMPTY_STRING # 代码
self.exchange = EMPTY_STRING # 交易所
self.price = EMPTY_FLOAT # 价格
self.volume = EMPTY_INT # 数量
self.priceType = EMPTY_STRING
self.direction = EMPTY_STRING
self.offset = EMPTY_STRING
self.priceType = EMPTY_STRING # 价格类型
self.direction = EMPTY_STRING # 买卖
self.offset = EMPTY_STRING # 开平
########################################################################
@ -425,13 +396,13 @@ class VtCancelOrderReq:
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
self.symbol = EMPTY_STRING
self.exchange = EMPTY_STRING
self.symbol = EMPTY_STRING # 代码
self.exchange = EMPTY_STRING # 交易所
# 以下字段主要和CTP、LTS类接口相关
self.orderRef = EMPTY_STRING
self.frontID = EMPTY_STRING
self.sessionID = EMPTY_STRING
self.orderID = EMPTY_STRING # 报单号
self.frontID = EMPTY_STRING # 前置机号
self.sessionID = EMPTY_STRING # 会话号

26
vn.trader/vtMain.py Normal file
View File

@ -0,0 +1,26 @@
# encoding: UTF-8
import sys
import ctypes
from vtEngine import MainEngine
from uiMainWindow import *
#----------------------------------------------------------------------
def main():
"""主程序入口"""
# 设置底部任务栏图标win7以下请注释掉
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID('vn.py demo')
app = QtGui.QApplication(sys.argv)
app.setWindowIcon(QtGui.QIcon('vnpy.ico'))
app.setFont(BASIC_FONT)
mainEngine = MainEngine()
mainWindow = MainWindow(mainEngine, mainEngine.eventEngine, mainEngine.dataEngine)
mainWindow.showMaximized()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

183
vn.trader/windGateway.py Normal file
View File

@ -0,0 +1,183 @@
# encoding: UTF-8
'''
Wind Python API的gateway接入
'''
from threading import Thread
try:
from WindPy import w
except ImportError:
print u'请先安装WindPy接口'
from vtGateway import *
# 交易所类型映射
exchangeMap = {}
exchangeMap[EXCHANGE_SSE] = 'SH'
exchangeMap[EXCHANGE_SZSE] = 'SZ'
exchangeMap[EXCHANGE_CFFEX] = 'CFE'
exchangeMap[EXCHANGE_SHFE] = 'SHF'
exchangeMap[EXCHANGE_DCE] = 'DCE'
exchangeMap[EXCHANGE_CZCE] = 'CZC'
exchangeMap[EXCHANGE_UNKNOWN] = ''
exchangeMapReverse = {v:k for k,v in exchangeMap.items()}
########################################################################
class WindGateway(VtGateway):
"""Wind接口"""
# 订阅wsq时传入的字段列表
wsqParamMap = {}
wsqParamMap['rt_last'] = 'lastPrice'
wsqParamMap['rt_last_vol'] = 'volume'
wsqParamMap['rt_oi'] = 'openInterest'
wsqParamMap['rt_open'] = 'openPrice'
wsqParamMap['rt_high'] = 'highPrice'
wsqParamMap['rt_low'] = 'lowPrice'
wsqParamMap['rt_pre_close'] = 'preClosePrice'
wsqParamMap['rt_high_limit'] = 'upperLimit'
wsqParamMap['rt_low_limit'] = 'lowerLimit'
wsqParamMap['rt_bid1'] = 'bidPrice1'
wsqParamMap['rt_bid2'] = 'bidPrice2'
wsqParamMap['rt_bid3'] = 'bidPrice3'
wsqParamMap['rt_bid4'] = 'bidPrice4'
wsqParamMap['rt_bid5'] = 'bidPrice5'
wsqParamMap['rt_ask1'] = 'askPrice1'
wsqParamMap['rt_ask2'] = 'askPrice2'
wsqParamMap['rt_ask3'] = 'askPrice3'
wsqParamMap['rt_ask4'] = 'askPrice4'
wsqParamMap['rt_ask5'] = 'askPrice5'
wsqParamMap['rt_bsize1'] = 'bidVolume1'
wsqParamMap['rt_bsize2'] = 'bidVolume2'
wsqParamMap['rt_bsize3'] = 'bidVolume3'
wsqParamMap['rt_bsize4'] = 'bidVolume4'
wsqParamMap['rt_bsize5'] = 'bidVolume5'
wsqParamMap['rt_asize1'] = 'askVolume1'
wsqParamMap['rt_asize2'] = 'askVolume2'
wsqParamMap['rt_asize3'] = 'askVolume3'
wsqParamMap['rt_asize4'] = 'askVolume4'
wsqParamMap['rt_asize5'] = 'askVolume5'
wsqParam = ','.join(wsqParamMap.keys())
#----------------------------------------------------------------------
def __init__(self, eventEngine, gatewayName='Wind'):
"""Constructor"""
super(WindGateway, self).__init__(eventEngine, gatewayName)
self.w = w # Wind API对象
self.connected = False # 连接状态
# Wind的wsq更新采用的是增量更新模式每次推送只会更新发生变化的字段
# 而vt中的tick是完整更新因此需要本地维护一个所有字段的快照
self.tickDict = {}
self.registerEvent()
#----------------------------------------------------------------------
def connect(self):
"""连接"""
# 由于w.start方法会阻塞较长时间
# 因此设计为异步模式,交给事件处理线程去处理
# 另外w.start和WingIDE的debug模块有冲突会导致异常退出
event = Event(type_=EVENT_WIND_CONNECTREQ)
self.eventEngine.put(event)
#----------------------------------------------------------------------
def subscribe(self, subscribeReq):
"""订阅行情"""
windSymbol = '.'.join([subscribeReq.symbol, exchangeMap[subscribeReq.exchange]])
data = self.w.wsq(windSymbol, self.wsqParam, func=self.wsqCallBack)
#----------------------------------------------------------------------
def sendOrder(self, orderReq):
"""发单"""
log = VtLogData()
log.gatewayName = self.gatewayName
log.logContent = u'Wind接口未实现发单功能'
self.onLog(log)
#----------------------------------------------------------------------
def cancelOrder(self, cancelOrderReq):
"""撤单"""
log = VtLogData()
log.gatewayName = self.gatewayName
log.logContent = u'Wind接口未实现撤单功能'
self.onLog(log)
#----------------------------------------------------------------------
def getAccount(self):
"""查询账户资金"""
log = VtLogData()
log.gatewayName = self.gatewayName
log.logContent = u'Wind接口未实现查询账户功能'
self.onLog(log)
#----------------------------------------------------------------------
def getPosition(self):
"""查询持仓"""
log = VtLogData()
log.gatewayName = self.gatewayName
log.logContent = u'Wind接口未实现查询持仓功能'
self.onLog(log)
#----------------------------------------------------------------------
def close(self):
self.w.stop()
#----------------------------------------------------------------------
def registerEvent(self):
"""注册事件监听"""
self.eventEngine.register(EVENT_WIND_CONNECTREQ, self.wConnect)
#----------------------------------------------------------------------
def wsqCallBack(self, data):
"""收到wsq推送"""
windSymbol = data.Codes[0]
if windSymbol in self.tickDict:
tick = self.tickDict[windSymbol]
else:
tick = VtTickData()
tick.gatewayName = self.gatewayName
symbolSplit = windSymbol.split('.')
tick.symbol = symbolSplit[0]
tick.exchange = exchangeMapReverse[symbolSplit[1]]
tick.vtSymbol = '.'.join([tick.symbol, tick.exchange])
self.tickDict[windSymbol] = tick
dt = data.Times[0]
tick.time = dt.strftime('%H:%M:%S')
tick.date = dt.strftime('%Y%m%d')
# 采用遍历的形式读取数值
fields = data.Fields
values = data.Data
d = tick.__dict__
for n, field in enumerate(fields):
field = field.lower()
key = self.wsqParamMap[field]
value = values[n][0]
d[key] = value
self.onTick(tick)
#----------------------------------------------------------------------
def wConnect(self, event):
"""利用事件处理线程去异步连接Wind接口"""
result = self.w.start()
log = VtLogData()
log.gatewayName = self.gatewayName
if not result.ErrorCode:
log.logContent = u'Wind接口连接成功'
else:
log.logContent = u'Wind接口连接失败错误代码%d' %result.ErrorCode
self.onLog(log)