Merge branch 'v2.0-DEV' of https://github.com/vnpy/vnpy into travis_test

This commit is contained in:
nanoric 2019-01-07 02:45:45 -04:00
commit e0942debbc
10 changed files with 1150 additions and 0 deletions

2
.gitignore vendored
View File

@ -4,6 +4,8 @@
# IDE
.vscode
.idea
*.wpr
*.wpu
# Temp
build

View File

@ -0,0 +1,2 @@
pyqt
qdarkstyle

View File

@ -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
View 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
View 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
View 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
View 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
View 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}"

View 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
View 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
}
}