一、介绍
在上章中介绍了pyQt4整个运行的流程,该章摘取的是vnpy中关于MainWindow窗口的布局。源代码中有个父类uiBasicWidget.py内容是布局中需要的单元格和表格类。
二、代码
uiBasicWidget.py主要是布局组件的代码。待Cell的类为单元格类,后面Monitor是大的组件表格类。整个代码主要部分有qt的connect事件绑定及触发,界面右键菜单的实现,单元格双击事件的connect,以及qt中基本的组件。
# encoding: UTF-8
import json
import csv
import os
import platform
from collections import OrderedDict
from vnpy.event import *
from vnpy.trader.vtEvent import *
from vnpy.trader.vtFunction import *
from vnpy.trader.vtGateway import *
from vnpy.trader import vtText
from vnpy.trader.uiQt import QtGui, QtWidgets, QtCore, BASIC_FONT
COLOR_RED = QtGui.QColor('red')
COLOR_GREEN = QtGui.QColor('green')
########################################################################
class BasicCell(QtWidgets.QTableWidgetItem):
"""基础的单元格"""
#----------------------------------------------------------------------
def __init__(self, text=None, mainEngine=None):
"""Constructor"""
super(BasicCell, self).__init__()
self.data = None
if text:
self.setContent(text)
#----------------------------------------------------------------------
def setContent(self, text):
"""设置内容"""
if text == '0' or text == '0.0':
self.setText('')
else:
self.setText(text)
########################################################################
class BasicMonitor(QtWidgets.QTableWidget):
"""
基础监控
headerDict中的值对应的字典格式如下
{'chinese': u'中文名', 'cellType': BasicCell}
"""
signal = QtCore.Signal(type(Event()))
#----------------------------------------------------------------------
def __init__(self, mainEngine=None, eventEngine=None, parent=None):
"""Constructor"""
super(BasicMonitor, self).__init__(parent)
self.mainEngine = mainEngine
self.eventEngine = eventEngine
# 保存表头标签用
self.headerDict = OrderedDict() # 有序字典,key是英文名,value是对应的配置字典
self.headerList = [] # 对应self.headerDict.keys()
# 保存相关数据用
self.dataDict = {} # 字典,key是字段对应的数据,value是保存相关单元格的字典
self.dataKey = '' # 字典键对应的数据字段
# 监控的事件类型
self.eventType = ''
# 列宽调整状态(只在第一次更新数据时调整一次列宽)
self.columnResized = False
# 字体
self.font = None
# 保存数据对象到单元格
self.saveData = False
# 默认不允许根据表头进行排序,需要的组件可以开启
self.sorting = False
# 初始化右键菜单
self.initMenu()
#----------------------------------------------------------------------
def setHeaderDict(self, headerDict):
"""设置表头有序字典"""
self.headerDict = headerDict
self.headerList = headerDict.keys()
#----------------------------------------------------------------------
def setDataKey(self, dataKey):
"""设置数据字典的键"""
self.dataKey = dataKey
#----------------------------------------------------------------------
def setEventType(self, eventType):
"""设置监控的事件类型"""
self.eventType = eventType
#----------------------------------------------------------------------
def setFont(self, font):
"""设置字体"""
self.font = font
#----------------------------------------------------------------------
def setSaveData(self, saveData):
"""设置是否要保存数据到单元格"""
self.saveData = saveData
#----------------------------------------------------------------------
def initTable(self):
"""初始化表格"""
# 设置表格的列数
col = len(self.headerDict)
self.setColumnCount(col)
# 设置列表头
labels = [d['chinese'] for d in self.headerDict.values()]
self.setHorizontalHeaderLabels(labels)
# 关闭左边的垂直表头
self.verticalHeader().setVisible(False)
# 设为不可编辑
self.setEditTriggers(self.NoEditTriggers)
# 设为行交替颜色
self.setAlternatingRowColors(True)
# 设置允许排序
self.setSortingEnabled(self.sorting)
#----------------------------------------------------------------------
def registerEvent(self):
"""注册GUI更新相关的事件监听"""
#此处qtpy自身的事件引擎self.signal.connect绑定了事件函数,而self.eventEngine是vnpy中自定义的事件引擎。当该eventEngine事件引擎被触发时会调用self.signal.emit发出信号函数,再由qtpy自身的事件引擎处理调用self.updateEvent函数。
self.signal.connect(self.updateEvent)
self.eventEngine.register(self.eventType, self.signal.emit)
#----------------------------------------------------------------------
def updateEvent(self, event):
"""收到事件更新"""
data = event.dict_['data']
self.updateData(data)
#----------------------------------------------------------------------
def updateData(self, data):
"""将数据更新到表格中"""
# 如果允许了排序功能,则插入数据前必须关闭,否则插入新的数据会变乱
if self.sorting:
self.setSortingEnabled(False)
# 如果设置了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 = safeUnicode(data.__getattribute__(header))
cellType = self.headerDict[header]['cellType']
cell = cellType(content, self.mainEngine)
if self.font:
cell.setFont(self.font) # 如果设置了特殊字体,则进行单元格设置
if self.saveData: # 如果设置了保存数据对象,则进行对象保存
cell.data = data
self.setItem(0, n, cell)
d[header] = cell
self.dataDict[key] = d
# 否则如果已经存在,则直接更新相关单元格
else:
d = self.dataDict[key]
for header in self.headerList:
content = safeUnicode(data.__getattribute__(header))
cell = d[header]
cell.setContent(content)
if self.saveData: # 如果设置了保存数据对象,则进行对象保存
cell.data = data
# 否则采用增量更新模式
else:
self.insertRow(0)
for n, header in enumerate(self.headerList):
content = safeUnicode(data.__getattribute__(header))
cellType = self.headerDict[header]['cellType']
cell = cellType(content, self.mainEngine)
if self.font:
cell.setFont(self.font)
if self.saveData:
cell.data = data
self.setItem(0, n, cell)
# 调整列宽
if not self.columnResized:
self.resizeColumns()
self.columnResized = True
# 重新打开排序
if self.sorting:
self.setSortingEnabled(True)
#----------------------------------------------------------------------
def resizeColumns(self):
"""调整各列的大小"""
self.horizontalHeader().resizeSections(QtWidgets.QHeaderView.ResizeToContents)
#----------------------------------------------------------------------
def setSorting(self, sorting):
"""设置是否允许根据表头排序"""
self.sorting = sorting
#----------------------------------------------------------------------
def saveToCsv(self):
"""保存表格内容到CSV文件"""
# 先隐藏右键菜单
self.menu.close()
# 获取想要保存的文件名
path = QtWidgets.QFileDialog.getSaveFileName(self, vtText.SAVE_DATA, '', 'CSV(*.csv)')
try:
#if not path.isEmpty():
if path:
with open(unicode(path), 'wb') as f:
writer = csv.writer(f)
# 保存标签
headers = [header.encode('gbk') for header in self.headerList]
writer.writerow(headers)
# 保存每行内容
for row in range(self.rowCount()):
rowdata = []
for column in range(self.columnCount()):
item = self.item(row, column)
if item is not None:
rowdata.append(
unicode(item.text()).encode('gbk'))
else:
rowdata.append('')
writer.writerow(rowdata)
except IOError:
pass
#----------------------------------------------------------------------
def initMenu(self):
"""初始化右键菜单"""
#qt邮件菜单,先实例化菜单和菜单内容项,将菜单内容项saveAction链接到相应事件,再将saveAction加入到菜单对象menu中。
self.menu = QtWidgets.QMenu(self)
saveAction = QtWidgets.QAction(vtText.SAVE_DATA, self)
saveAction.triggered.connect(self.saveToCsv)
self.menu.addAction(saveAction)
#----------------------------------------------------------------------
def contextMenuEvent(self, event):
"""右键点击事件"""
self.menu.popup(QtGui.QCursor.pos())
########################################################################
class OrderMonitor(BasicMonitor):
"""委托监控"""
#----------------------------------------------------------------------
def __init__(self, mainEngine, eventEngine, parent=None):
"""Constructor"""
super(OrderMonitor, self).__init__(mainEngine, eventEngine, parent)
self.mainEngine = mainEngine
d = OrderedDict()
d['orderID'] = {'chinese':vtText.ORDER_ID, 'cellType':NumCell}
d['symbol'] = {'chinese':vtText.CONTRACT_SYMBOL, 'cellType':BasicCell}
d['vtSymbol'] = {'chinese':vtText.CONTRACT_NAME, 'cellType':NameCell}
d['direction'] = {'chinese':vtText.DIRECTION, 'cellType':DirectionCell}
d['offset'] = {'chinese':vtText.OFFSET, 'cellType':BasicCell}
d['price'] = {'chinese':vtText.PRICE, 'cellType':BasicCell}
d['totalVolume'] = {'chinese':vtText.ORDER_VOLUME, 'cellType':BasicCell}
d['tradedVolume'] = {'chinese':vtText.TRADED_VOLUME, 'cellType':BasicCell}
d['status'] = {'chinese':vtText.ORDER_STATUS, 'cellType':BasicCell}
d['orderTime'] = {'chinese':vtText.ORDER_TIME, 'cellType':BasicCell}
d['cancelTime'] = {'chinese':vtText.CANCEL_TIME, 'cellType':BasicCell}
#d['frontID'] = {'chinese':vtText.FRONT_ID, 'cellType':BasicCell} # 考虑到在vn.trader中,ctpGateway的报单号应该是始终递增的,因此这里可以忽略
#d['sessionID'] = {'chinese':vtText.SESSION_ID, 'cellType':BasicCell}
d['gatewayName'] = {'chinese':vtText.GATEWAY, 'cellType':BasicCell}
self.setHeaderDict(d)
self.setDataKey('vtOrderID')
self.setEventType(EVENT_ORDER)
self.setFont(BASIC_FONT)
self.setSaveData(True)
self.setSorting(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):
"""持仓监控"""
#----------------------------------------------------------------------
def __init__(self, mainEngine, eventEngine, parent=None):
"""Constructor"""
super(PositionMonitor, self).__init__(mainEngine, eventEngine, parent)
d = OrderedDict()
d['symbol'] = {'chinese':vtText.CONTRACT_SYMBOL, 'cellType':BasicCell}
d['vtSymbol'] = {'chinese':vtText.CONTRACT_NAME, 'cellType':NameCell}
d['direction'] = {'chinese':vtText.DIRECTION, 'cellType':DirectionCell}
d['position'] = {'chinese':vtText.POSITION, 'cellType':BasicCell}
d['ydPosition'] = {'chinese':vtText.YD_POSITION, 'cellType':BasicCell}
d['frozen'] = {'chinese':vtText.FROZEN, 'cellType':BasicCell}
d['price'] = {'chinese':vtText.PRICE, 'cellType':BasicCell}
d['positionProfit'] = {'chinese':vtText.POSITION_PROFIT, 'cellType':PnlCell}
d['gatewayName'] = {'chinese':vtText.GATEWAY, 'cellType':BasicCell}
self.setHeaderDict(d)
self.setDataKey('vtPositionName')
self.setEventType(EVENT_POSITION)
self.setFont(BASIC_FONT)
self.setSaveData(True)
self.initTable()
self.registerEvent()
########################################################################
class AccountMonitor(BasicMonitor):
"""账户监控"""
#----------------------------------------------------------------------
def __init__(self, mainEngine, eventEngine, parent=None):
"""Constructor"""
super(AccountMonitor, self).__init__(mainEngine, eventEngine, parent)
d = OrderedDict()
d['accountID'] = {'chinese':vtText.ACCOUNT_ID, 'cellType':BasicCell}
d['preBalance'] = {'chinese':vtText.PRE_BALANCE, 'cellType':BasicCell}
d['balance'] = {'chinese':vtText.BALANCE, 'cellType':BasicCell}
d['available'] = {'chinese':vtText.AVAILABLE, 'cellType':BasicCell}
d['commission'] = {'chinese':vtText.COMMISSION, 'cellType':BasicCell}
d['margin'] = {'chinese':vtText.MARGIN, 'cellType':BasicCell}
d['closeProfit'] = {'chinese':vtText.CLOSE_PROFIT, 'cellType':BasicCell}
d['positionProfit'] = {'chinese':vtText.POSITION_PROFIT, 'cellType':BasicCell}
d['gatewayName'] = {'chinese':vtText.GATEWAY, 'cellType':BasicCell}
self.setHeaderDict(d)
self.setDataKey('vtAccountID')
self.setEventType(EVENT_ACCOUNT)
self.setFont(BASIC_FONT)
self.initTable()
self.registerEvent()
########################################################################
class TradingWidget(QtWidgets.QFrame):
"""简单交易组件"""
signal = QtCore.Signal(type(Event()))
directionList = [DIRECTION_LONG,
DIRECTION_SHORT]
offsetList = [OFFSET_OPEN,
OFFSET_CLOSE,
OFFSET_CLOSEYESTERDAY,
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,
EXCHANGE_SGE,
EXCHANGE_HKEX,
EXCHANGE_HKFE,
EXCHANGE_SMART,
EXCHANGE_ICE,
EXCHANGE_CME,
EXCHANGE_NYMEX,
EXCHANGE_LME,
EXCHANGE_GLOBEX,
EXCHANGE_IDEALPRO]
currencyList = [CURRENCY_NONE,
CURRENCY_CNY,
CURRENCY_HKD,
CURRENCY_USD]
productClassList = [PRODUCT_NONE,
PRODUCT_EQUITY,
PRODUCT_FUTURES,
PRODUCT_OPTION,
PRODUCT_FOREX]
gatewayList = ['']
#----------------------------------------------------------------------
def __init__(self, mainEngine, eventEngine, parent=None):
"""Constructor"""
super(TradingWidget, self).__init__(parent)
self.mainEngine = mainEngine
self.eventEngine = eventEngine
self.symbol = ''
# 添加交易接口
l = mainEngine.getAllGatewayDetails()
gatewayNameList = [d['gatewayName'] for d in l]
self.gatewayList.extend(gatewayNameList)
self.initUi()
self.connectSignal()
#----------------------------------------------------------------------
def initUi(self):
"""初始化界面"""
self.setWindowTitle(vtText.TRADING)
self.setMaximumWidth(400)
self.setFrameShape(self.Box) # 设置边框
self.setLineWidth(1)
# 左边部分
labelSymbol = QtWidgets.QLabel(vtText.CONTRACT_SYMBOL)
labelName = QtWidgets.QLabel(vtText.CONTRACT_NAME)
labelDirection = QtWidgets.QLabel(vtText.DIRECTION)
labelOffset = QtWidgets.QLabel(vtText.OFFSET)
labelPrice = QtWidgets.QLabel(vtText.PRICE)
self.checkFixed = QtWidgets.QCheckBox(u'') # 价格固定选择框
labelVolume = QtWidgets.QLabel(vtText.VOLUME)
labelPriceType = QtWidgets.QLabel(vtText.PRICE_TYPE)
labelExchange = QtWidgets.QLabel(vtText.EXCHANGE)
labelCurrency = QtWidgets.QLabel(vtText.CURRENCY)
labelProductClass = QtWidgets.QLabel(vtText.PRODUCT_CLASS)
labelGateway = QtWidgets.QLabel(vtText.GATEWAY)
self.lineSymbol = QtWidgets.QLineEdit()
self.lineName = QtWidgets.QLineEdit()
self.comboDirection = QtWidgets.QComboBox()
self.comboDirection.addItems(self.directionList)
self.comboOffset = QtWidgets.QComboBox()
self.comboOffset.addItems(self.offsetList)
self.spinPrice = QtWidgets.QDoubleSpinBox()
self.spinPrice.setDecimals(4)
self.spinPrice.setMinimum(0)
self.spinPrice.setMaximum(100000)
self.spinVolume = QtWidgets.QSpinBox()
self.spinVolume.setMinimum(0)
self.spinVolume.setMaximum(1000000)
self.comboPriceType = QtWidgets.QComboBox()
self.comboPriceType.addItems(self.priceTypeList)
self.comboExchange = QtWidgets.QComboBox()
self.comboExchange.addItems(self.exchangeList)
self.comboCurrency = QtWidgets.QComboBox()
self.comboCurrency.addItems(self.currencyList)
self.comboProductClass = QtWidgets.QComboBox()
self.comboProductClass.addItems(self.productClassList)
self.comboGateway = QtWidgets.QComboBox()
self.comboGateway.addItems(self.gatewayList)
gridleft = QtWidgets.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(labelCurrency, 8, 0)
gridleft.addWidget(labelProductClass, 9, 0)
gridleft.addWidget(labelGateway, 10, 0)
gridleft.addWidget(self.lineSymbol, 0, 1, 1, -1)
gridleft.addWidget(self.lineName, 1, 1, 1, -1)
gridleft.addWidget(self.comboDirection, 2, 1, 1, -1)
gridleft.addWidget(self.comboOffset, 3, 1, 1, -1)
gridleft.addWidget(self.checkFixed, 4, 1)
gridleft.addWidget(self.spinPrice, 4, 2)
gridleft.addWidget(self.spinVolume, 5, 1, 1, -1)
gridleft.addWidget(self.comboPriceType, 6, 1, 1, -1)
gridleft.addWidget(self.comboExchange, 7, 1, 1, -1)
gridleft.addWidget(self.comboCurrency, 8, 1, 1, -1)
gridleft.addWidget(self.comboProductClass, 9, 1, 1, -1)
gridleft.addWidget(self.comboGateway, 10, 1, 1, -1)
# 右边部分
labelBid1 = QtWidgets.QLabel(vtText.BID_1)
labelBid2 = QtWidgets.QLabel(vtText.BID_2)
labelBid3 = QtWidgets.QLabel(vtText.BID_3)
labelBid4 = QtWidgets.QLabel(vtText.BID_4)
labelBid5 = QtWidgets.QLabel(vtText.BID_5)
labelAsk1 = QtWidgets.QLabel(vtText.ASK_1)
labelAsk2 = QtWidgets.QLabel(vtText.ASK_2)
labelAsk3 = QtWidgets.QLabel(vtText.ASK_3)
labelAsk4 = QtWidgets.QLabel(vtText.ASK_4)
labelAsk5 = QtWidgets.QLabel(vtText.ASK_5)
self.labelBidPrice1 = QtWidgets.QLabel()
self.labelBidPrice2 = QtWidgets.QLabel()
self.labelBidPrice3 = QtWidgets.QLabel()
self.labelBidPrice4 = QtWidgets.QLabel()
self.labelBidPrice5 = QtWidgets.QLabel()
self.labelBidVolume1 = QtWidgets.QLabel()
self.labelBidVolume2 = QtWidgets.QLabel()
self.labelBidVolume3 = QtWidgets.QLabel()
self.labelBidVolume4 = QtWidgets.QLabel()
self.labelBidVolume5 = QtWidgets.QLabel()
self.labelAskPrice1 = QtWidgets.QLabel()
self.labelAskPrice2 = QtWidgets.QLabel()
self.labelAskPrice3 = QtWidgets.QLabel()
self.labelAskPrice4 = QtWidgets.QLabel()
self.labelAskPrice5 = QtWidgets.QLabel()
self.labelAskVolume1 = QtWidgets.QLabel()
self.labelAskVolume2 = QtWidgets.QLabel()
self.labelAskVolume3 = QtWidgets.QLabel()
self.labelAskVolume4 = QtWidgets.QLabel()
self.labelAskVolume5 = QtWidgets.QLabel()
labelLast = QtWidgets.QLabel(vtText.LAST)
self.labelLastPrice = QtWidgets.QLabel()
self.labelReturn = QtWidgets.QLabel()
self.labelLastPrice.setMinimumWidth(60)
self.labelReturn.setMinimumWidth(60)
gridRight = QtWidgets.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 = QtWidgets.QPushButton(vtText.SEND_ORDER)
buttonCancelAll = QtWidgets.QPushButton(vtText.CANCEL_ALL)
size = buttonSendOrder.sizeHint()
buttonSendOrder.setMinimumHeight(size.height()*2) # 把按钮高度设为默认两倍
buttonCancelAll.setMinimumHeight(size.height()*2)
# 整合布局
hbox = QtWidgets.QHBoxLayout()
hbox.addLayout(gridleft)
hbox.addLayout(gridRight)
vbox = QtWidgets.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 = str(self.lineSymbol.text())
exchange = unicode(self.comboExchange.currentText())
currency = unicode(self.comboCurrency.currentText())
productClass = unicode(self.comboProductClass.currentText())
gatewayName = unicode(self.comboGateway.currentText())
# 查询合约
if exchange:
vtSymbol = '.'.join([symbol, exchange])
contract = self.mainEngine.getContract(vtSymbol)
else:
vtSymbol = symbol
contract = self.mainEngine.getContract(symbol)
if contract:
vtSymbol = contract.vtSymbol
gatewayName = contract.gatewayName
self.lineName.setText(contract.name)
exchange = contract.exchange # 保证有交易所代码
# 清空价格数量
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 + vtSymbol, self.signal.emit)
# 订阅合约
req = VtSubscribeReq()
req.symbol = symbol
req.exchange = exchange
req.currency = currency
req.productClass = productClass
# 默认跟随价
self.checkFixed.setChecked(False)
self.mainEngine.subscribe(req, gatewayName)
# 更新组件当前交易的合约
self.symbol = vtSymbol
#----------------------------------------------------------------------
def updateTick(self, event):
"""更新行情"""
tick = event.dict_['data']
if tick.vtSymbol == self.symbol:
if not self.checkFixed.isChecked():
self.spinPrice.setValue(tick.lastPrice)
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))
if tick.preClosePrice:
rt = (tick.lastPrice/tick.preClosePrice)-1
self.labelReturn.setText(('%.2f' %(rt*100))+'%')
else:
self.labelReturn.setText('')
#----------------------------------------------------------------------
def connectSignal(self):
"""连接Signal"""
self.signal.connect(self.updateTick)
#----------------------------------------------------------------------
def sendOrder(self):
"""发单"""
symbol = str(self.lineSymbol.text())
exchange = unicode(self.comboExchange.currentText())
currency = unicode(self.comboCurrency.currentText())
productClass = unicode(self.comboProductClass.currentText())
gatewayName = unicode(self.comboGateway.currentText())
# 查询合约
if exchange:
vtSymbol = '.'.join([symbol, exchange])
contract = self.mainEngine.getContract(vtSymbol)
else:
vtSymbol = symbol
contract = self.mainEngine.getContract(symbol)
if contract:
gatewayName = contract.gatewayName
exchange = contract.exchange # 保证有交易所代码
req = VtOrderReq()
req.symbol = symbol
req.exchange = 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())
req.currency = currency
req.productClass = productClass
self.mainEngine.sendOrder(req, gatewayName)
#----------------------------------------------------------------------
def cancelAll(self):
"""一键撤销所有委托"""
l = self.mainEngine.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)
#----------------------------------------------------------------------
def closePosition(self, cell):
"""根据持仓信息自动填写交易组件"""
# 读取持仓数据,cell是一个表格中的单元格对象
pos = cell.data
symbol = pos.symbol
# 更新交易组件的显示合约
self.lineSymbol.setText(symbol)
self.updateSymbol()
# 自动填写信息
self.comboPriceType.setCurrentIndex(self.priceTypeList.index(PRICETYPE_LIMITPRICE))
self.comboOffset.setCurrentIndex(self.offsetList.index(OFFSET_CLOSE))
self.spinVolume.setValue(pos.position)
if pos.direction == DIRECTION_LONG or pos.direction == DIRECTION_NET:
self.comboDirection.setCurrentIndex(self.directionList.index(DIRECTION_SHORT))
else:
self.comboDirection.setCurrentIndex(self.directionList.index(DIRECTION_LONG))
# 价格留待更新后由用户输入,防止有误操作
MainWindow.py主要内容有主窗口菜单的创建,如何将Basic中组件加载到主窗口中,自定义Qtpy信号通道及触发,以及如何退出主窗口,和如何保存界面设置及其恢复。
# encoding: UTF-8
# python跨平台库,用于获取系统进程,进运行信息(CPU,运存,磁盘,网络等)
import psutil
from vnpy.trader.vtFunction import loadIconPath
from vnpy.trader.vtGlobal import globalSetting
from vnpy.trader.uiBasicWidget import *
########################################################################
class MainWindow(QtWidgets.QMainWindow):
"""主窗口"""
# 自定义信号变量,用于连接槽函数,并可主动触发.
signalStatusBar = QtCore.Signal(type(Event()))
#----------------------------------------------------------------------
def __init__(self, mainEngine, eventEngine):
"""Constructor"""
super(MainWindow, self).__init__()
self.mainEngine = mainEngine
self.eventEngine = eventEngine
l = self.mainEngine.getAllGatewayDetails()
self.gatewayNameList = [d['gatewayName'] for d in l]
self.widgetDict = {} # 用来保存子窗口的字典
# 获取主引擎中的上层应用信息
self.appDetailList = self.mainEngine.getAllAppDetails()
self.initUi()
self.loadWindowSettings('custom')
#----------------------------------------------------------------------
def initUi(self):
"""初始化界面"""
self.setWindowTitle('VnTrader')
self.initCentral()
self.initMenu()
self.initStatusBar()
#----------------------------------------------------------------------
def initCentral(self):
"""初始化中心区域"""
widgetMarketM, dockMarketM = self.createDock(MarketMonitor, vtText.MARKET_DATA, QtCore.Qt.RightDockWidgetArea)
widgetLogM, dockLogM = self.createDock(LogMonitor, vtText.LOG, QtCore.Qt.BottomDockWidgetArea)
widgetErrorM, dockErrorM = self.createDock(ErrorMonitor, vtText.ERROR, QtCore.Qt.BottomDockWidgetArea)
widgetTradeM, dockTradeM = self.createDock(TradeMonitor, vtText.TRADE, QtCore.Qt.BottomDockWidgetArea)
widgetOrderM, dockOrderM = self.createDock(OrderMonitor, vtText.ORDER, QtCore.Qt.RightDockWidgetArea)
widgetPositionM, dockPositionM = self.createDock(PositionMonitor, vtText.POSITION, QtCore.Qt.BottomDockWidgetArea)
widgetAccountM, dockAccountM = self.createDock(AccountMonitor, vtText.ACCOUNT, QtCore.Qt.BottomDockWidgetArea)
widgetTradingW, dockTradingW = self.createDock(TradingWidget, vtText.TRADING, QtCore.Qt.LeftDockWidgetArea)
self.tabifyDockWidget(dockTradeM, dockErrorM)
self.tabifyDockWidget(dockTradeM, dockLogM)
self.tabifyDockWidget(dockPositionM, dockAccountM)
dockTradeM.raise_()
dockPositionM.raise_()
# 连接组件之间的信号
widgetPositionM.itemDoubleClicked.connect(widgetTradingW.closePosition)
# 保存默认设置
self.saveWindowSettings('default')
#----------------------------------------------------------------------
def initMenu(self):
"""初始化菜单"""
# 创建菜单
menubar = self.menuBar()
# 设计为只显示存在的接口
gatewayDetails = self.mainEngine.getAllGatewayDetails()
sysMenu = menubar.addMenu(vtText.SYSTEM)
for d in gatewayDetails:
if d['gatewayType'] == GATEWAYTYPE_FUTURES:
self.addConnectAction(sysMenu, d['gatewayName'], d['gatewayDisplayName'])
sysMenu.addSeparator()
for d in gatewayDetails:
if d['gatewayType'] == GATEWAYTYPE_EQUITY:
self.addConnectAction(sysMenu, d['gatewayName'], d['gatewayDisplayName'])
sysMenu.addSeparator()
for d in gatewayDetails:
if d['gatewayType'] == GATEWAYTYPE_INTERNATIONAL:
self.addConnectAction(sysMenu, d['gatewayName'], d['gatewayDisplayName'])
sysMenu.addSeparator()
for d in gatewayDetails:
if d['gatewayType'] == GATEWAYTYPE_BTC:
self.addConnectAction(sysMenu, d['gatewayName'], d['gatewayDisplayName'])
sysMenu.addSeparator()
for d in gatewayDetails:
if d['gatewayType'] == GATEWAYTYPE_DATA:
self.addConnectAction(sysMenu, d['gatewayName'], d['gatewayDisplayName'])
sysMenu.addSeparator()
sysMenu.addAction(self.createAction(vtText.CONNECT_DATABASE, self.mainEngine.dbConnect, loadIconPath('database.ico')))
sysMenu.addSeparator()
sysMenu.addAction(self.createAction(vtText.EXIT, self.close, loadIconPath('exit.ico')))
# 功能应用
appMenu = menubar.addMenu(vtText.APPLICATION)
for appDetail in self.appDetailList:
function = self.createOpenAppFunction(appDetail)
action = self.createAction(appDetail['appDisplayName'], function, loadIconPath(appDetail['appIco']))
appMenu.addAction(action)
# 帮助
helpMenu = menubar.addMenu(vtText.HELP)
helpMenu.addAction(self.createAction(vtText.CONTRACT_SEARCH, self.openContract, loadIconPath('contract.ico')))
helpMenu.addSeparator()
helpMenu.addAction(self.createAction(vtText.RESTORE, self.restoreWindow, loadIconPath('restore.ico')))
helpMenu.addAction(self.createAction(vtText.ABOUT, self.openAbout, loadIconPath('about.ico')))
helpMenu.addSeparator()
helpMenu.addAction(self.createAction(vtText.TEST, self.test, loadIconPath('test.ico')))
#----------------------------------------------------------------------
def initStatusBar(self):
"""初始化状态栏"""
self.statusLabel = QtWidgets.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.signalStatusBar.connect(self.updateStatusBar)
self.eventEngine.register(EVENT_TIMER, self.signalStatusBar.emit)
#----------------------------------------------------------------------
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 vtText.CPU_MEMORY_INFO.format(cpu=cpuPercent, memory=memoryPercent)
#----------------------------------------------------------------------
def addConnectAction(self, menu, gatewayName, displayName=''):
"""增加连接功能"""
if gatewayName not in self.gatewayNameList:
return
def connect():
self.mainEngine.connect(gatewayName)
if not displayName:
displayName = gatewayName
actionName = vtText.CONNECT + displayName
connectAction = self.createAction(actionName, connect,
loadIconPath('connect.ico'))
menu.addAction(connectAction)
#----------------------------------------------------------------------
def createAction(self, actionName, function, iconPath=''):
"""创建操作功能"""
action = QtWidgets.QAction(actionName, self)
action.triggered.connect(function)
if iconPath:
icon = QtGui.QIcon(iconPath)
action.setIcon(icon)
return action
#----------------------------------------------------------------------
def createOpenAppFunction(self, appDetail):
"""创建打开应用UI的函数"""
def openAppFunction():
appName = appDetail['appName']
try:
self.widgetDict[appName].show()
except KeyError:
appEngine = self.mainEngine.appDict[appName]
self.widgetDict[appName] = appDetail['appWidget'](appEngine, self.eventEngine)
self.widgetDict[appName].show()
return openAppFunction
#----------------------------------------------------------------------
def test(self):
"""测试按钮用的函数"""
# 有需要使用手动触发的测试函数可以写在这里
pass
#----------------------------------------------------------------------
def openAbout(self):
"""打开关于"""
try:
self.widgetDict['aboutW'].show()
except KeyError:
self.widgetDict['aboutW'] = AboutWidget(self)
self.widgetDict['aboutW'].show()
#----------------------------------------------------------------------
def openContract(self):
"""打开合约查询"""
try:
self.widgetDict['contractM'].show()
except KeyError:
self.widgetDict['contractM'] = ContractManager(self.mainEngine)
self.widgetDict['contractM'].show()
#----------------------------------------------------------------------
def closeEvent(self, event):
"""关闭事件"""
reply = QtWidgets.QMessageBox.question(self, vtText.EXIT,
vtText.CONFIRM_EXIT, QtWidgets.QMessageBox.Yes |
QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
for widget in self.widgetDict.values():
widget.close()
self.saveWindowSettings('custom')
# 关闭主窗口
self.mainEngine.exit()
event.accept()
else:
event.ignore()
#----------------------------------------------------------------------
def createDock(self, widgetClass, widgetName, widgetArea):
"""创建停靠组件"""
widget = widgetClass(self.mainEngine, self.eventEngine)
dock = QtWidgets.QDockWidget(widgetName)
dock.setWidget(widget)
dock.setObjectName(widgetName)
dock.setFeatures(dock.DockWidgetFloatable|dock.DockWidgetMovable)
self.addDockWidget(widgetArea, dock)
return widget, dock
#----------------------------------------------------------------------
def saveWindowSettings(self, settingName):
"""保存窗口设置"""
settings = QtCore.QSettings('vn.trader', settingName)
settings.setValue('state', self.saveState())
settings.setValue('geometry', self.saveGeometry())
#----------------------------------------------------------------------
def loadWindowSettings(self, settingName):
"""载入窗口设置"""
settings = QtCore.QSettings('vn.trader', settingName)
# 这里由于PyQt4的版本不同,settings.value('state')调用返回的结果可能是:
# 1. None(初次调用,注册表里无相应记录,因此为空)
# 2. QByteArray(比较新的PyQt4)
# 3. QVariant(以下代码正确执行所需的返回结果)
# 所以为了兼容考虑,这里加了一个try...except,如果是1、2的情况就pass
# 可能导致主界面的设置无法载入(每次退出时的保存其实是成功了)
try:
self.restoreState(settings.value('state').toByteArray())
self.restoreGeometry(settings.value('geometry').toByteArray())
except AttributeError:
pass
#----------------------------------------------------------------------
def restoreWindow(self):
"""还原默认窗口设置(还原停靠组件位置)"""
self.loadWindowSettings('default')
self.showMaximized()
########################################################################
class AboutWidget(QtWidgets.QDialog):
"""显示关于信息"""
#----------------------------------------------------------------------
def __init__(self, parent=None):
"""Constructor"""
super(AboutWidget, self).__init__(parent)
self.initUi()
#----------------------------------------------------------------------
def initUi(self):
""""""
self.setWindowTitle(vtText.ABOUT + 'VnTrader')
text = u"""
Developed by Traders, for Traders.
License:MIT
Website:www.vnpy.org
Github:www.github.com/vnpy/vnpy
"""
label = QtWidgets.QLabel()
label.setText(text)
label.setMinimumWidth(500)
vbox = QtWidgets.QVBoxLayout()
vbox.addWidget(label)
self.setLayout(vbox)