[Add] Add trader ui widget
This commit is contained in:
parent
ca14973b9a
commit
e25787536a
21
vnpy/trader/ui/__init__.py
Normal file
21
vnpy/trader/ui/__init__.py
Normal file
@ -0,0 +1,21 @@
|
||||
import platform
|
||||
import ctypes
|
||||
|
||||
import qdarkstyle
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
from .mainwindow import MainWindow
|
||||
|
||||
|
||||
def create_qapp():
|
||||
"""
|
||||
Create Qt Application.
|
||||
"""
|
||||
qapp = QtWidgets.QApplication([])
|
||||
qapp.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
|
||||
|
||||
if 'Windows' in platform.uname():
|
||||
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
|
||||
'VN Trader')
|
||||
|
||||
return qapp
|
585
vnpy/trader/ui/widget.py
Normal file
585
vnpy/trader/ui/widget.py
Normal file
@ -0,0 +1,585 @@
|
||||
"""
|
||||
Basic widgets for VN Trader.
|
||||
"""
|
||||
|
||||
import csv
|
||||
from typing import Any
|
||||
|
||||
from PyQt5 import QtWidgets, QtGui, QtCore
|
||||
|
||||
from vnpy.event import EventEngine, Event
|
||||
from ..constant import DIRECTION_LONG, DIRECTION_SHORT, DIRECTION_NET
|
||||
from ..engine import MainEngine
|
||||
from ..event import (EVENT_TICK, EVENT_ORDER, EVENT_TRADE, EVENT_ACCOUNT,
|
||||
EVENT_POSITION, EVENT_CONTRACT, EVENT_LOG)
|
||||
|
||||
COLOR_LONG = QtGui.QColor("red")
|
||||
COLOR_SHORT = QtGui.QColor("green")
|
||||
COLOR_BID = QtGui.QColor(255, 174, 201)
|
||||
COLOR_ASK = QtGui.QColor(160, 255, 160)
|
||||
COLOR_BLACK = QtGui.QColor("black")
|
||||
|
||||
|
||||
class BaseCell(QtWidgets.QTableWidgetItem):
|
||||
"""
|
||||
General cell used in tablewidgets.
|
||||
"""
|
||||
|
||||
def __init__(self, content: Any, data: Any):
|
||||
""""""
|
||||
super(BaseCel, self).__init__()
|
||||
self.setTextAlignment(QtCore.Qt.AlignCenter)
|
||||
self.set_content(content, data)
|
||||
|
||||
def set_content(self, content: Any, data: Any):
|
||||
"""
|
||||
Set text content.
|
||||
"""
|
||||
self.setText(str(content))
|
||||
self._data = data
|
||||
|
||||
def get_data(self):
|
||||
"""
|
||||
Get data object.
|
||||
"""
|
||||
return self._data
|
||||
|
||||
|
||||
class DirectionCell(BaseCell):
|
||||
"""
|
||||
Cell used for showing direction data.
|
||||
"""
|
||||
|
||||
def __init__(self, content: str, data: Any):
|
||||
""""""
|
||||
super(DirectionCell, self).__init__(content, data)
|
||||
|
||||
def set_content(self, content: Any, data: Any):
|
||||
"""
|
||||
Cell color is set according to direction.
|
||||
"""
|
||||
super(DirectionCell, self).set_content(content, data)
|
||||
|
||||
if content == DIRECTION_SHORT:
|
||||
self.setForeground(COLOR_SHORT)
|
||||
else:
|
||||
self.setForeground(COLOR_LONG)
|
||||
|
||||
|
||||
class BidCell(BaseCell):
|
||||
"""
|
||||
Cell used for showing bid price and volume.
|
||||
"""
|
||||
|
||||
def __init__(self, content: Any, data: Any):
|
||||
""""""
|
||||
super(BidCell, self).__init__(content, data)
|
||||
|
||||
self.setForeground(COLOR_BLACK)
|
||||
self.setForeground(COLOR_BID)
|
||||
|
||||
|
||||
class AskCell(BaseCell):
|
||||
"""
|
||||
Cell used for showing ask price and volume.
|
||||
"""
|
||||
|
||||
def __init__(self, content: Any, data: Any):
|
||||
""""""
|
||||
super(AskCell, self).__init__(content, data)
|
||||
|
||||
self.setForeground(COLOR_BLACK)
|
||||
self.setForeground(COLOR_ASK)
|
||||
|
||||
|
||||
class PnlCell(BaseCell):
|
||||
"""
|
||||
Cell used for showing pnl data.
|
||||
"""
|
||||
|
||||
def __init__(self, content: Any, data: Any):
|
||||
""""""
|
||||
super(PnlCell, self).__init__(content, data)
|
||||
|
||||
def set_content(self, content: Any, data: Any):
|
||||
"""
|
||||
Cell color is set based on whether pnl is
|
||||
positive or negative.
|
||||
"""
|
||||
super(PnlCell, self).set_content(content, data)
|
||||
|
||||
if content.startswith("-"):
|
||||
self.setForeground(COLOR_SHORT)
|
||||
else:
|
||||
self.setForeground(COLOR_LONG)
|
||||
|
||||
|
||||
class TimeCell(BaseCell):
|
||||
"""
|
||||
Cell used for showing time string from datetime object.
|
||||
"""
|
||||
|
||||
def __init__(self, content: Any, data: Any):
|
||||
""""""
|
||||
super(TimeCell, self).__init__(content, data)
|
||||
|
||||
def set_content(self, content: Any, data: Any):
|
||||
"""
|
||||
Time format is 12:12:12.5
|
||||
"""
|
||||
timestamp = content.strftime("%H:%M:%S")
|
||||
|
||||
millisecond = int(content.microsecond / 1000)
|
||||
if millisecond:
|
||||
timestamp = f"{timestamp}.{millisecond}"
|
||||
|
||||
self.setText(timestamp)
|
||||
self._data = data
|
||||
|
||||
|
||||
class BaseMonitor(QtWidgets.QTableWidget):
|
||||
"""
|
||||
Monitor data update in VN Trader.
|
||||
"""
|
||||
|
||||
event_type = ""
|
||||
data_key = ""
|
||||
sorting = False
|
||||
headers = {}
|
||||
|
||||
signal = QtCore.pyqtSignal(Event)
|
||||
|
||||
def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
|
||||
""""""
|
||||
super(BaseMonitor, self).__init__()
|
||||
|
||||
self.main_engine = main_engine
|
||||
self.event_engine = event_engine
|
||||
self.cells = {}
|
||||
|
||||
self.init_ui()
|
||||
self.register_event()
|
||||
|
||||
def init_ui(self):
|
||||
""""""
|
||||
self.init_table()
|
||||
self.init_menu()
|
||||
|
||||
def init_table(self):
|
||||
"""
|
||||
Initialize table.
|
||||
"""
|
||||
self.setColumnCount(len(self.headers))
|
||||
|
||||
labels = [d['display'] for d in self.headers.values()]
|
||||
self.setHorizontalHeaderLabels(labels)
|
||||
|
||||
self.verticalHeader().setVisible(False)
|
||||
self.setEditTriggers(self.NoEditTriggers)
|
||||
self.setAlternatingRowColors(True)
|
||||
self.setSortingEnabled(self.sorting)
|
||||
|
||||
def init_menu(self):
|
||||
"""
|
||||
Create right click menu.
|
||||
"""
|
||||
self.menu = QtWidgets.QMenu(self)
|
||||
|
||||
resize_action = QtWidgets.QAction("调整列宽", self)
|
||||
resize_action.triggered.connect(self.resize_columns)
|
||||
self.menu.addAction(resize_action)
|
||||
|
||||
save_action = QtWidgets.QAction("保存数据", self)
|
||||
save_action.triggered.connect(self.save_csv)
|
||||
self.menu.addAction(save_action)
|
||||
|
||||
def register_event(self):
|
||||
"""
|
||||
Register event handler into event engine.
|
||||
"""
|
||||
self.signal.connect(self.process_event)
|
||||
self.event_engine.register(self.event_type, self.process_event)
|
||||
|
||||
def process_event(self, event):
|
||||
"""
|
||||
Process new data from event and update into table.
|
||||
"""
|
||||
# Disable sorting to prevent unwanted error.
|
||||
if self.sorting:
|
||||
self.setSortingEnabled(False)
|
||||
|
||||
# Update data into table.
|
||||
data = event.data
|
||||
|
||||
if not self.data_key:
|
||||
self.insert_new_row(data)
|
||||
else:
|
||||
key = data.__getattribute__(self.data_key)
|
||||
|
||||
if key in self.cells:
|
||||
self.update_old_row(data)
|
||||
else:
|
||||
self.insert_new_row(data)
|
||||
|
||||
# Enable sorting
|
||||
if self.sorting:
|
||||
self.setSortingEnabled(True)
|
||||
|
||||
def insert_new_row(self, data):
|
||||
"""
|
||||
Insert a new row at the top of table.
|
||||
"""
|
||||
self.insertRow(0)
|
||||
|
||||
row_cells = {}
|
||||
for column, header in enumerate(self.headers.keys()):
|
||||
setting = self.headers[header]
|
||||
|
||||
content = data.__getattribute__(header)
|
||||
cell = setting['cell'](content, data)
|
||||
self.setItem(0, column, cell)
|
||||
|
||||
if setting['update']:
|
||||
row_cells[header] = cell
|
||||
|
||||
if self.data_key:
|
||||
key = data.__getattribute__(self.data_key)
|
||||
self.cells[key] = row_cells
|
||||
|
||||
def update_old_row(self, data):
|
||||
"""
|
||||
Update an old row in table.
|
||||
"""
|
||||
key = data.__getattribute__(self.data_key)
|
||||
row_cells = self.cells[key]
|
||||
|
||||
for header, cell in row_cells:
|
||||
content = data.__getattribute__(header)
|
||||
cell.set_content(content, data)
|
||||
|
||||
def resize_columns(self):
|
||||
"""
|
||||
Resize all columns according to contents.
|
||||
"""
|
||||
self.horizontalHeader().resizeSections(
|
||||
QtWidgets.QHeaderView.ResizeToContents)
|
||||
|
||||
def save_csv(self):
|
||||
"""
|
||||
Save table data into a csv file
|
||||
"""
|
||||
path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "保存数据", "",
|
||||
"CSV(*.csv)")
|
||||
|
||||
if not path:
|
||||
return
|
||||
|
||||
with open(path, "wb") as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow(self.headers.keys())
|
||||
|
||||
for row in range(self.rowCount()):
|
||||
row_data = []
|
||||
for column in range(self.columnCount()):
|
||||
item = self.item(row, column)
|
||||
if item:
|
||||
row_data.append(text_type(item.text()))
|
||||
else:
|
||||
row_data.append("")
|
||||
writer.writerow(row_data)
|
||||
|
||||
|
||||
class TickMonitor(BaseMonitor):
|
||||
"""
|
||||
Monitor for tick data.
|
||||
"""
|
||||
event_type = EVENT_TICK
|
||||
data_key = "vt_symbol"
|
||||
sorting = True
|
||||
|
||||
headers = {
|
||||
'symbol': {
|
||||
'display': '代码',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
},
|
||||
'last_price': {
|
||||
'display': '最新价',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
},
|
||||
'volume': {
|
||||
'display': '成交量',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
},
|
||||
'open_price': {
|
||||
'display': '开盘价',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
},
|
||||
'high_price': {
|
||||
'display': '最高价',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
},
|
||||
'low_price': {
|
||||
'display': '最低价',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
},
|
||||
'bid_price_1': {
|
||||
'display': '买1价',
|
||||
'cell': BidCell,
|
||||
'update': True
|
||||
},
|
||||
'bid_volume_1': {
|
||||
'display': '买1量',
|
||||
'cell': BidCell,
|
||||
'update': True
|
||||
},
|
||||
'ask_price_1': {
|
||||
'display': '卖1价',
|
||||
'cell': AskCell,
|
||||
'update': True
|
||||
},
|
||||
'ask_volume_1': {
|
||||
'display': '卖1量',
|
||||
'cell': AskCell,
|
||||
'update': True
|
||||
},
|
||||
'time': {
|
||||
'display': '时间',
|
||||
'cell': TimeCell,
|
||||
'update': True
|
||||
},
|
||||
'gateway_name': {
|
||||
'display': '接口',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class LogMonitor(BaseMonitor):
|
||||
"""
|
||||
Monitor for log data.
|
||||
"""
|
||||
event_type = EVENT_LOG
|
||||
data_key = ""
|
||||
sorting = False
|
||||
|
||||
headers = {
|
||||
'time': {
|
||||
'display': '时间',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
},
|
||||
'msg': {
|
||||
'display': '信息',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
},
|
||||
'gateway_name': {
|
||||
'display': '接口',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
}
|
||||
}
|
||||
|
||||
def process_event(self, event):
|
||||
"""Resize row heights for diplaying long log message."""
|
||||
super(LogMonitor, self).process_event(event)
|
||||
self.resizeRowToContents(0)
|
||||
|
||||
|
||||
class TradeMonitor(BaseMonitor):
|
||||
"""
|
||||
Monitor for trade data.
|
||||
"""
|
||||
event_type = EVENT_TRADE
|
||||
data_key = ""
|
||||
sorting = True
|
||||
|
||||
headers = {
|
||||
'tradeid': {
|
||||
'display': "成交号 ",
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
},
|
||||
'orderid': {
|
||||
'display': '委托号',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
},
|
||||
'symbol': {
|
||||
'display': '代码',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
},
|
||||
'direction': {
|
||||
'display': '方向',
|
||||
'cell': DirectionCell,
|
||||
'update': False
|
||||
},
|
||||
'offset': {
|
||||
'display': '开平',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
},
|
||||
'price': {
|
||||
'display': '价格',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
},
|
||||
'volume': {
|
||||
'display': '数量',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
},
|
||||
'gateway_name': {
|
||||
'display': '接口',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class OrderMonitor(BaseMonitor):
|
||||
"""
|
||||
Monitor for order data.
|
||||
"""
|
||||
event_type = EVENT_ORDER
|
||||
data_key = "vt_orderid"
|
||||
sorting = True
|
||||
|
||||
headers = {
|
||||
'orderid': {
|
||||
'display': '委托号',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
},
|
||||
'symbol': {
|
||||
'display': '代码',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
},
|
||||
'direction': {
|
||||
'display': '方向',
|
||||
'cell': DirectionCell,
|
||||
'update': False
|
||||
},
|
||||
'offset': {
|
||||
'display': '开平',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
},
|
||||
'price': {
|
||||
'display': '价格',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
},
|
||||
'volume': {
|
||||
'display': '总数量',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
},
|
||||
'traded': {
|
||||
'display': '已成交',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
},
|
||||
'status': {
|
||||
'display': '状态',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
},
|
||||
'time': {
|
||||
'display': '时间',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
},
|
||||
'gateway_name': {
|
||||
'display': '接口',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
}
|
||||
}
|
||||
|
||||
class PositionMonitor(BaseMonitor):
|
||||
"""
|
||||
Monitor for position data.
|
||||
"""
|
||||
event_type = EVENT_POSITION
|
||||
data_key = "vt_positionid"
|
||||
sorting = True
|
||||
|
||||
headers = {
|
||||
'symbol': {
|
||||
'display': '代码',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
},
|
||||
'direction': {
|
||||
'display': '方向',
|
||||
'cell': DirectionCell,
|
||||
'update': False
|
||||
},
|
||||
'volume': {
|
||||
'display': '数量',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
},
|
||||
'frozen': {
|
||||
'display': '冻结',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
},
|
||||
'price': {
|
||||
'display': '均价',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
},
|
||||
'pnl': {
|
||||
'display': '盈亏',
|
||||
'cell': PnlCell,
|
||||
'update': True
|
||||
},
|
||||
'gateway_name': {
|
||||
'display': '接口',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AccountMonitor(BaseMonitor):
|
||||
"""
|
||||
Monitor for account data.
|
||||
"""
|
||||
event_type = EVENT_ACCOUNT
|
||||
data_key = "vt_accountid"
|
||||
sorting = True
|
||||
|
||||
headers = {
|
||||
'accountid': {
|
||||
'display': '账号',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
},
|
||||
'balance': {
|
||||
'display': '余额',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
},
|
||||
'frozen': {
|
||||
'display': '冻结',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
},
|
||||
'available': {
|
||||
'display': '可用',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user