diff --git a/.gitignore b/.gitignore index 9c033465..eeec9ee7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ # IDE .vscode .idea +*.wpr +*.wpu # Temp build diff --git a/requirements.txt b/requirements.txt index e69de29b..c212511b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -0,0 +1,2 @@ +pyqt +qdarkstyle \ No newline at end of file diff --git a/vnpy/event/engine.py b/vnpy/event/engine.py index 625c6222..c90dc412 100644 --- a/vnpy/event/engine.py +++ b/vnpy/event/engine.py @@ -1,3 +1,7 @@ +""" +Event-driven framework of vn.py framework. +""" + from queue import Queue, Empty from threading import Thread, Timer from collections import defaultdict diff --git a/vnpy/trader/constant.py b/vnpy/trader/constant.py new file mode 100644 index 00000000..cd1e53e3 --- /dev/null +++ b/vnpy/trader/constant.py @@ -0,0 +1,55 @@ +""" +General constant string used in VN Trader. +""" + +DIRECTION_LONG = '多' +DIRECTION_SHORT = '空' +DIRECTION_NET = '净' + +OFFSET_OPEN = '开' +OFFSET_CLOSE = '平' +OFFSET_CLOSETODAY = '平今' +OFFSET_CLOSEYESTERDAY = '平昨' + +STATUS_SUBMITTING = '提交中' +STATUS_NOTTRADED = '未成交' +STATUS_PARTTRADED = '部分成交' +STATUS_ALLTRADED = '全部成交' +STATUS_CANCELLED = '已撤销' +STATUS_REJECTED = '拒单' + +PRODUCT_EQUITY = '股票' +PRODUCT_FUTURES = '期货' +PRODUCT_OPTION = '期权' +PRODUCT_INDEX = '指数' +PRODUCT_FOREX = '外汇' +PRODUCT_SPOT = '现货' + +PRICETYPE_LIMIT = '限价' +PRICETYPE_MARKET = '市价' +PRICETYPE_FAK = 'FAK' +PRICETYPE_FOK = 'FOK' + +OPTION_CALL = '看涨期权' +OPTION_PUT = '看跌期权' + +EXCHANGE_SSE = 'SSE' +EXCHANGE_SZSE = 'SZSE' +EXCHANGE_CFFEX = 'CFFEX' +EXCHANGE_SHFE = 'SHFE' +EXCHANGE_CZCE = 'CZCE' +EXCHANGE_DCE = 'DCE' +EXCHANGE_INE = 'INE' +EXCHANGE_SGE = 'SGE' + +CURRENCY_USD = 'USD' +CURRENCY_HKD = 'HKD' + +INTERVAL_1M = '1分钟' +INTERVAL_5M = '5分钟' +INTERVAL_15M = '15分钟' +INTERVAL_30M = '30分钟' +INTERVAL_1H = '1小时' +INTERVAL_4H = '4小时' +INTERVAL_DAILY = '日线' +INTERVAL_WEEKLY = '周线' \ No newline at end of file diff --git a/vnpy/trader/engine.py b/vnpy/trader/engine.py new file mode 100644 index 00000000..a6f2da85 --- /dev/null +++ b/vnpy/trader/engine.py @@ -0,0 +1,83 @@ +""" +""" + +from vnpy.event import EventEngine, Event + +from .event import EVENT_LOG +from .object import LogData, SubscribeRequest, OrderRequest, CancelRequest + + +class MainEngine: + """ + Acts as the core of VN Trader. + """ + + def __init__(self, event_engine: EventEngine = None): + """""" + if event_engine: + self.event_engine = event_engine + else: + self.event_engine = EventEngine() + self.event_engine.start() + + self.gateways = {} + self.apps = {} + + def write_log(self, msg: str): + """ + Put log event with specific message. + """ + log = LogData(msg=msg) + event = Event(EVENT_LOG, log) + self.event_engine.put(event) + + def get_gateway(gateway_name: str): + """ + Return gateway object by name. + """ + gateway = self.gateways.get(gateway_name, None) + if not gateway: + self.write_log(f"找不到底层接口:{gateway_name}") + return gateway + + def connect(self, gateway_name: str): + """ + Start connection of a specific gateway. + """ + gateway = self.get_gateway(gateway_name) + if gateway: + gateway.connect() + + def subscribe(self, req: SubscribeRequest, gateway_name: str): + """ + Subscribe tick data update of a specific gateway. + """ + gateway = self.get_gateway(gateway_name) + if gateway: + gateway.subscribe(req) + + def send_order(self, req: OrderRequest, gateway_name: str): + """ + Send new order request to a specific gateway. + """ + gateway = self.get_gateway(gateway_name) + if gateway: + gateway.send_order(req) + + def cancel_order(self, req: CancelRequest, gateway_name: str): + """ + Send cancel order request to a specific gateway. + """ + gateway = self.get_gateway(gateway_name) + if gateway: + gateway.send_order(req) + + def close(self): + """ + Make sure every gateway and app is closed properly before + programme exit. + """ + for gateway in self.gateways.values(): + gateway.close() + + self.event_engine.stop() diff --git a/vnpy/trader/event.py b/vnpy/trader/event.py new file mode 100644 index 00000000..c679a971 --- /dev/null +++ b/vnpy/trader/event.py @@ -0,0 +1,13 @@ +""" +Event type string used in VN Trader. +""" + +from vnpy.event import EVENT_TIMER + +EVENT_TICK = 'eTick.' +EVENT_TRADE = 'eTrade.' +EVENT_ORDER = 'eOrder.' +EVENT_POSITION = 'ePosition.' +EVENT_ACCOUNT = 'eAccount.' +EVENT_CONTRACT = 'eContract.' +EVENT_LOG = 'eLog' diff --git a/vnpy/trader/gateway.py b/vnpy/trader/gateway.py new file mode 100644 index 00000000..4706d725 --- /dev/null +++ b/vnpy/trader/gateway.py @@ -0,0 +1,133 @@ +""" + +""" + +from abc import ABC, abstractmethod + +from vnpy.event import EventEngine, Event + +from .event import (EVENT_TICK, EVENT_ORDER, EVENT_TRADE, EVENT_ACCOUNT, + EVENT_POSITION, EVENT_LOG, EVENT_CONTRACT) +from .object import (TickData, OrderData, TradeData, AccountData, PositionData, + LogData, ContractData, SubscribeRequest, OrderRequest, + CancelRequest) + + +class Gateway(ABC): + """ + Abstract gateway class for creating gateways connection + to different trading systems. + """ + + def __init__(self, event_engine: EventEngine, gateway_name: str): + """""" + self.event_engine = event_engine + self.gateway_name = gateway_name + + def on_event(self, type: str, data: Any = None): + """ + General event push. + """ + event = Event(type, data) + self.event_engine.put(event) + + def on_tick(self, tick: TickData): + """ + Tick event push. + Tick event of a specific vt_symbol is also pushed. + """ + self.on_event(EVENT_TICK, tick) + self.on_event(EVENT_TICK + tick.vt_symbol, tick) + + def on_trade(self, trade: TradeData): + """ + Trade event push. + Trade event of a specific vt_symbol is also pushed. + """ + self.on_event(EVENT_TRADE, trade) + self.on_event(EVENT_TRADE + trade.vt_symbol, trade) + + def on_order(self, order: OrderData): + """ + Order event push. + Order event of a specific vt_orderid is also pushed. + """ + self.on_event(EVENT_ORDER, order) + self.on_event(EVENT_ORDER + order.vt_orderid, order) + + def on_position(self, position: PositionData): + """ + Position event push. + Position event of a specific vt_symbol is also pushed. + """ + self.on_event(EVENT_POSITION, position) + self.on_event(EVENT_POSITION + position.vt_symbol, position) + + def on_account(self, account: AccountData): + """ + Account event push. + Account event of a specific vt_accountid is also pushed. + """ + self.on_event(EVENT_ACCOUNT, account) + self.on_event(EVENT_ACCOUNT + account.vt_accountid, account) + + def on_log(self, log: LogData): + """ + Log event push. + """ + self.on_event(EVENT_LOG, log) + + def on_contract(self, contract: ContractData): + """ + Contract event push. + """ + self.on_event(EVENT_CONTRACT, contract) + + @abstractmethod + def connect(self): + """ + Start gateway connection. + """ + pass + + @abstractmethod + def close(self): + """ + Close gateway connection. + """ + pass + + @abstractmethod + def subscribe(self, req: SubscribeRequest): + """ + Subscribe tick data update. + """ + pass + + @abstractmethod + def send_order(self, req: OrderRequest): + """ + Send a new order. + """ + pass + + @abstractmethod + def cancel_order(self, req: CancelRequest): + """ + Cancel an existing order. + """ + pass + + @abstractmethod + def query_account(self): + """ + Query account balance. + """ + pass + + @abstractmethod + def query_position(self): + """ + Query holding positions. + """ + pass \ No newline at end of file diff --git a/vnpy/trader/object.py b/vnpy/trader/object.py new file mode 100644 index 00000000..ccf56024 --- /dev/null +++ b/vnpy/trader/object.py @@ -0,0 +1,252 @@ +""" +Basic data structure used for general trading function in VN Trader. +""" + +from dataclasses import dataclass +from datetime import datetime +from logging import INFO + + +@dataclass +class BaseData: + """ + Any data object needs a gateway_name as source or + destination and should inherit base data. + """ + gateway_name: str + + +@dataclass +class TickData(BaseData): + """ + Tick data contains information about: + * last trade in market + * orderbook snapshot + * intraday market statistics. + """ + symbol: str + exchange: str + datetime: datetime + volume: float = 0 + + last_price: float = 0 + last_volume: float = 0 + + open_price: float = 0 + high_price: float = 0 + low_price: float = 0 + pre_close: float = 0 + + bid_price_1: float = 0 + bid_price_2: float = 0 + bid_price_3: float = 0 + bid_price_4: float = 0 + bid_price_5: float = 0 + + ask_price_1: float = 0 + ask_price_2: float = 0 + ask_price_3: float = 0 + ask_price_4: float = 0 + ask_price_5: float = 0 + + bid_volume_1: float = 0 + bid_volume_2: float = 0 + bid_volume_3: float = 0 + bid_volume_4: float = 0 + bid_volume_5: float = 0 + + ask_volume_1: float = 0 + ask_volume_2: float = 0 + ask_volume_3: float = 0 + ask_volume_4: float = 0 + ask_volume_5: float = 0 + + def __post_init__(self): + """""" + self.vt_symbol = f"{self.symbol}.{self.exchange}" + + +@dataclass +class BarData(BaseData): + """ + Candlestick bar data of a certain trading period. + """ + symbol: str + exchange: str + datetime: datetime + inteval: str + + volume: float = 0 + open_price: float = 0 + high_price: float = 0 + low_price: float = 0 + close_price: float = 0 + + def __post_init__(self): + """""" + self.vt_symbol = f"{self.symbol}.{self.exchange}" + + +@dataclass +class OrderData(BaseData): + """ + Order data contains information for tracking lastest status + of a specific order. + """ + symbol: str + exchange: str + orderid: str + + direction: str = "" + offset: str = "" + price: float = 0 + volume: float = 0 + traded: float = 0 + status: str = "" + time: str = "" + + def __post_init__(self): + """""" + self.vt_symbol = f"{self.symbol}.{self.exchange}" + self.vt_orderid = f"{self.gateway_name}.{self.orderid}" + + +@dataclass +class TradeData(BaseData): + """ + Trade data contains information of a fill of an order. One order + can have several trade fills. + """ + symbol: str + exchange: str + orderid: str + tradeid: str + + direction: str = "" + offset: str = "" + price: float = 0 + volume: float = 0 + time: str = "" + + def __post_init__(self): + """""" + self.vt_symbol = f"{self.symbol}.{self.exchange}" + self.vt_orderid = f"{self.gateway_name}.{self.orderid}" + self.vt_tradeid = f"{self.gateway_name}.{self.tradeid}" + + +@dataclass +class PositionData(BaseData): + """ + Positon data is used for tracking each individual position holding. + """ + symbol: str + exchange: str + direction: str + + volume: float = 0 + frozen: float = 0 + price: float = 0 + pnl: float = 0 + + def __post_init__(self): + """""" + self.vt_symbol = f"{self.symbol}.{self.exchange}" + self.vt_positionid = f"{self.vt_symbol}.{self.direction}" + + +@dataclass +class AccountData(BaseData): + """ + Account data contains information about balance, frozen and + available. + """ + accountid: str + + balance: float = 0 + frozen: float = 0 + + def __post_init__(self): + """""" + self.available = self.balance - self.frozen + self.vt_accountid = f"{self.gateway_name}.{self.accountid}" + + +@dataclass +class LogData(BaseData): + """ + Log data is used for recording log messages on GUI or in log files. + """ + msg: str + level: int = INFO + + def __post_init__(self): + """""" + time = datetime + + +@dataclass +class ContractData(BaseData): + """ + Contract data contains basic information about each contract traded. + """ + symbol: str + exchange: str + name: str + product: str + size: int + pricetick: float + + option_strike: float = 0 + option_underlying: str = '' # vt_symbol of underlying contract + option_type: str = '' + option_expiry: datetime = None + + def __post_init__(self): + """""" + self.vt_symbol = f"{self.symbol}.{self.exchange}" + + +@dataclass +class SubscribeRequest: + """ + Request sending to specific gateway for subscribing tick data update. + """ + symbol: str + exchange: str = '' + + def __post_init__(self): + """""" + self.vt_symbol = f"{self.symbol}.{self.exchange}" + + +@dataclass +class OrderRequest: + """ + Request sending to specific gateway for creating a new order. + """ + symbol: str + direction: str + price_type: str + volume: float + exchange: str = '' + price: float = 0 + offset: str = '' + + def __post_init__(self): + """""" + self.vt_symbol = f"{self.symbol}.{self.exchange}" + + +@dataclass +class CancelRequest: + """ + Request sending to specific gateway for canceling an existing order. + """ + orderid: str + symbol: str = '' + exchange: str = '' + + def __post_init__(self): + """""" + self.vt_symbol = f"{self.symbol}.{self.exchange}" diff --git a/vnpy/trader/ui/__init__.py b/vnpy/trader/ui/__init__.py new file mode 100644 index 00000000..709d9260 --- /dev/null +++ b/vnpy/trader/ui/__init__.py @@ -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 \ No newline at end of file diff --git a/vnpy/trader/ui/widget.py b/vnpy/trader/ui/widget.py new file mode 100644 index 00000000..b692a038 --- /dev/null +++ b/vnpy/trader/ui/widget.py @@ -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 + } + }