Merge branch 'v2.0-DEV' of https://github.com/vnpy/vnpy into travis_test
This commit is contained in:
commit
e0942debbc
2
.gitignore
vendored
2
.gitignore
vendored
@ -4,6 +4,8 @@
|
||||
# IDE
|
||||
.vscode
|
||||
.idea
|
||||
*.wpr
|
||||
*.wpu
|
||||
|
||||
# Temp
|
||||
build
|
||||
|
@ -0,0 +1,2 @@
|
||||
pyqt
|
||||
qdarkstyle
|
@ -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
|
||||
|
55
vnpy/trader/constant.py
Normal file
55
vnpy/trader/constant.py
Normal file
@ -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 = '周线'
|
83
vnpy/trader/engine.py
Normal file
83
vnpy/trader/engine.py
Normal file
@ -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()
|
13
vnpy/trader/event.py
Normal file
13
vnpy/trader/event.py
Normal file
@ -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'
|
133
vnpy/trader/gateway.py
Normal file
133
vnpy/trader/gateway.py
Normal file
@ -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
|
252
vnpy/trader/object.py
Normal file
252
vnpy/trader/object.py
Normal file
@ -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}"
|
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