[Add] Complete ib gateway development
This commit is contained in:
parent
84eaeb8733
commit
64c1c3ccde
@ -1,2 +1,2 @@
|
||||
pyqt
|
||||
PyQt5
|
||||
qdarkstyle
|
@ -1,8 +1,11 @@
|
||||
from vnpy.event import EventEngine
|
||||
from vnpy.trader.engine import MainEngine
|
||||
from vnpy.trader.ui import MainWindow, create_qapp
|
||||
from vnpy.gateway.ib import IbGateway
|
||||
|
||||
from vnpy.trader.ui.widget import TickMonitor
|
||||
import os
|
||||
import logging
|
||||
import time
|
||||
|
||||
|
||||
def main():
|
||||
@ -10,7 +13,9 @@ def main():
|
||||
qapp = create_qapp()
|
||||
|
||||
event_engine = EventEngine()
|
||||
|
||||
main_engine = MainEngine(event_engine)
|
||||
main_engine.add_gateway(IbGateway)
|
||||
|
||||
main_window = MainWindow(main_engine, event_engine)
|
||||
main_window.showMaximized()
|
||||
|
0
vnpy/gateway/__init__.py
Normal file
0
vnpy/gateway/__init__.py
Normal file
@ -1 +1 @@
|
||||
from ib_gateway import IbGateway
|
||||
from .ib_gateway import IbGateway
|
@ -3,15 +3,19 @@ Please install ibapi from Interactive Brokers github page.
|
||||
"""
|
||||
from datetime import datetime
|
||||
from copy import copy
|
||||
from threading import Thread
|
||||
from queue import Empty
|
||||
|
||||
from ibapi.wrapper import EWrapper
|
||||
from ibapi.client import EClient
|
||||
from ibapi.contract import Contract, ContractDetails
|
||||
from ibapi.order import Order
|
||||
from ibapi.common import TickerId, OrderId, TickAttrib
|
||||
from ibapi.common import TickerId, OrderId, TickAttrib, MAX_MSG_LEN
|
||||
from ibapi.ticktype import TickType
|
||||
from ibapi.order_state import OrderState
|
||||
from ibapi.execution import Execution
|
||||
from ibapi.utils import BadMessage
|
||||
from ibapi import comm
|
||||
|
||||
from vnpy.trader.gateway import BaseGateway
|
||||
from vnpy.trader.object import (
|
||||
@ -62,7 +66,9 @@ PRICETYPE_VT2IB = {PRICETYPE_LIMIT: "LMT", PRICETYPE_MARKET: "MKT"}
|
||||
PRICETYPE_IB2VT = {v: k for k, v in PRICETYPE_VT2IB.items()}
|
||||
|
||||
DIRECTION_VT2IB = {DIRECTION_LONG: "BUY", DIRECTION_SHORT: "SELL"}
|
||||
DIRECTION_IB2VT = {v: k for k, v in DIRECTION_IB2VT.items()}
|
||||
DIRECTION_IB2VT = {v: k for k, v in DIRECTION_VT2IB.items()}
|
||||
DIRECTION_IB2VT["BOT"] = DIRECTION_LONG
|
||||
DIRECTION_IB2VT["SLD"] = DIRECTION_SHORT
|
||||
|
||||
EXCHANGE_VT2IB = {
|
||||
EXCHANGE_SMART: "SMART",
|
||||
@ -91,7 +97,7 @@ PRODUCT_VT2IB = {
|
||||
PRODUCT_OPTION: "OPT",
|
||||
PRODUCT_FUTURES: "FUT"
|
||||
}
|
||||
PRODUCT_IB2VT = {v: k for k,v in PRODUCT_VT2IB.items()}
|
||||
PRODUCT_IB2VT = {v: k for k, v in PRODUCT_VT2IB.items()}
|
||||
|
||||
OPTION_VT2IB = {OPTION_CALL: "CALL", OPTION_PUT: "PUT"}
|
||||
|
||||
@ -123,15 +129,11 @@ ACCOUNTFIELD_IB2VT = {
|
||||
class IbGateway(BaseGateway):
|
||||
""""""
|
||||
|
||||
default_setting = {
|
||||
"host": "127.0.0.1",
|
||||
"port": 7497,
|
||||
"clientid": 1
|
||||
}
|
||||
default_setting = {"host": "127.0.0.1", "port": 7497, "clientid": 1}
|
||||
|
||||
def __init__(self, event_engine):
|
||||
""""""
|
||||
super(Gateway, self).__init__(event_engine, "IB")
|
||||
super(IbGateway, self).__init__(event_engine, "IB")
|
||||
|
||||
self.api = IbApi(self)
|
||||
|
||||
@ -181,7 +183,7 @@ class IbGateway(BaseGateway):
|
||||
class IbApi(EWrapper):
|
||||
""""""
|
||||
|
||||
def __init__(self, gateway: Gateway):
|
||||
def __init__(self, gateway: BaseGateway):
|
||||
""""""
|
||||
super(IbApi, self).__init__()
|
||||
|
||||
@ -189,15 +191,36 @@ class IbApi(EWrapper):
|
||||
self.gateway_name = gateway.gateway_name
|
||||
|
||||
self.status = False
|
||||
|
||||
self.reqid = 0
|
||||
self.orderid = 0
|
||||
self.clientid = 0
|
||||
self.ticks = {}
|
||||
self.orders = {}
|
||||
self.accounts = {}
|
||||
self.contracts = {}
|
||||
|
||||
self.client = EClient(self)
|
||||
self.tick_exchange = {}
|
||||
|
||||
self.client = IbClient(self)
|
||||
self.thread = Thread(target=self.client.run)
|
||||
|
||||
def connectAck(self):
|
||||
"""
|
||||
Callback when connection is established.
|
||||
"""
|
||||
self.status = True
|
||||
|
||||
log = LogData(msg="IB TWS连接成功", gateway_name=self.gateway_name)
|
||||
self.gateway.on_log(log)
|
||||
|
||||
def connectionClosed(self):
|
||||
"""
|
||||
Callback when connection is closed.
|
||||
"""
|
||||
self.status = False
|
||||
|
||||
log = LogData(msg="IB TWS连接断开", gateway_name=self.gateway_name)
|
||||
self.gateway.on_log(log)
|
||||
|
||||
def nextValidId(self, orderId: int):
|
||||
"""
|
||||
@ -213,12 +236,10 @@ class IbApi(EWrapper):
|
||||
"""
|
||||
super(IbApi, self).currentTime(time)
|
||||
|
||||
self.status = True
|
||||
|
||||
dt = datetime.fromtimestamp(time)
|
||||
time_string = dt.strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||
log = LogData(
|
||||
msg=f"IB TWS连接成功,服务器时间: {time_string}",
|
||||
msg=f"服务器时间: {time_string}",
|
||||
gateway_name=self.gateway_name
|
||||
)
|
||||
self.gateway.on_log(log)
|
||||
@ -229,7 +250,7 @@ class IbApi(EWrapper):
|
||||
"""
|
||||
super(IbApi, self).error(reqId, errorCode, errorString)
|
||||
log = LogData(
|
||||
msg=f"发生错误,错误代码:{errorCode},错误信息: {errorString}",
|
||||
msg=f"信息通知,代码:{errorCode},内容: {errorString}",
|
||||
gateway_name=self.gateway_name
|
||||
)
|
||||
self.gateway.on_log(log)
|
||||
@ -253,10 +274,10 @@ class IbApi(EWrapper):
|
||||
name = TICKFIELD_IB2VT[tickType]
|
||||
setattr(tick, name, price)
|
||||
|
||||
# Forex and spot product of IB has no tick time and last price.
|
||||
# Forex and spot product of IDEALPRO has no tick time and last price.
|
||||
# We need to calculate locally.
|
||||
contract = self.contracts[reqId]
|
||||
if contract.product in (PRODUCT_FOREX, PRODUCT_SPOT):
|
||||
exchange = self.tick_exchange[reqId]
|
||||
if exchange == EXCHANGE_IDEALPRO:
|
||||
tick.last_price = (tick.bid_price_1 + tick.ask_price_1) / 2
|
||||
tick.datetime = datetime.now()
|
||||
self.gateway.on_tick(copy(tick))
|
||||
@ -323,7 +344,7 @@ class IbApi(EWrapper):
|
||||
)
|
||||
|
||||
orderid = str(orderId)
|
||||
order = self.orders.get[orderid]
|
||||
order = self.orders.get(orderid, None)
|
||||
order.status = STATUS_IB2VT[status]
|
||||
order.traded = filled
|
||||
|
||||
@ -349,12 +370,13 @@ class IbApi(EWrapper):
|
||||
ib_contract.exchange
|
||||
),
|
||||
orderid=orderid,
|
||||
direction=DIRECTION_IB2V[ib_order.action],
|
||||
price=order.lmtPrice,
|
||||
volume=order.totalQuantity,
|
||||
direction=DIRECTION_IB2VT[ib_order.action],
|
||||
price=ib_order.lmtPrice,
|
||||
volume=ib_order.totalQuantity,
|
||||
gateway_name=self.gateway_name
|
||||
)
|
||||
|
||||
self.orders[orderid] = order
|
||||
self.gateway.on_order(copy(order))
|
||||
|
||||
def updateAccountValue(
|
||||
@ -379,6 +401,7 @@ class IbApi(EWrapper):
|
||||
accountid=accountid,
|
||||
gateway_name=self.gateway_name
|
||||
)
|
||||
self.accounts[accountid] = account
|
||||
|
||||
name = ACCOUNTFIELD_IB2VT[key]
|
||||
setattr(account, name, float(val))
|
||||
@ -426,7 +449,6 @@ class IbApi(EWrapper):
|
||||
Callback of account update time.
|
||||
"""
|
||||
super(IbApi, self).updateAccountTime(timeStamp)
|
||||
|
||||
for account in self.accounts.values():
|
||||
self.gateway.on_account(copy(account))
|
||||
|
||||
@ -435,6 +457,7 @@ class IbApi(EWrapper):
|
||||
Callback of contract data update.
|
||||
"""
|
||||
super(IbApi, self).contractDetails(reqId, contractDetails)
|
||||
|
||||
ib_symbol = contractDetails.contract.conId
|
||||
ib_exchange = contractDetails.contract.exchange
|
||||
ib_size = contractDetails.contract.multiplier
|
||||
@ -445,30 +468,32 @@ class IbApi(EWrapper):
|
||||
exchange=EXCHANGE_IB2VT.get(ib_exchange,
|
||||
ib_exchange),
|
||||
name=contractDetails.longName,
|
||||
product=PRODUCT_VT2IB[ib_product]
|
||||
product=PRODUCT_IB2VT[ib_product],
|
||||
size=ib_size,
|
||||
pricetick=contractDetails.minTick,
|
||||
gateway_name=self.gateway_name
|
||||
)
|
||||
self.gateway.on_contract(contract)
|
||||
|
||||
|
||||
def execDetails(self, reqId: int, contract: Contract, execution: Execution):
|
||||
"""
|
||||
Callback of trade data update.
|
||||
"""
|
||||
super(IbApi, self).execDetails(reqId, contract, execution)
|
||||
|
||||
|
||||
today_date = datetime.now().strftime("%Y%m%d")
|
||||
trade = TradeData(
|
||||
symbol=contract.conId,
|
||||
exchange=EXCHANGE_IB2VT.get(contract.exchange, contract.exchange)
|
||||
orderid = str(execution.orderId),
|
||||
exchange=EXCHANGE_IB2VT.get(contract.exchange,
|
||||
contract.exchange),
|
||||
orderid=str(execution.orderId),
|
||||
tradeid=str(execution.execId),
|
||||
direction=DIRECTION_IB2VT[execution.side],
|
||||
price=execution.price,
|
||||
volume=execution.shares,
|
||||
time=datetime.strptime(f"{today_date} {execution.time}", "%Y%m%d %H:%M:%S"),
|
||||
gateway_name = self.gateway_name
|
||||
time=datetime.strptime(execution.time,
|
||||
"%Y%m%d %H:%M:%S"),
|
||||
gateway_name=self.gateway_name
|
||||
)
|
||||
|
||||
self.gateway.on_trade(trade)
|
||||
@ -478,39 +503,50 @@ class IbApi(EWrapper):
|
||||
Callback of all sub accountid.
|
||||
"""
|
||||
super(IbApi, self).managedAccounts(accountsList)
|
||||
|
||||
|
||||
for account_code in accountsList.split(","):
|
||||
self.client.reqAccountUpdates(account_code)
|
||||
|
||||
self.client.reqAccountUpdates(True, account_code)
|
||||
|
||||
def connect(self, setting: dict):
|
||||
"""
|
||||
Connect to TWS.
|
||||
"""
|
||||
if self.status:
|
||||
return
|
||||
|
||||
self.clientid = setting["clientid"]
|
||||
|
||||
self.client.connect(
|
||||
setting["host"].
|
||||
setting["host"],
|
||||
setting["port"],
|
||||
setting["clientid"]
|
||||
)
|
||||
|
||||
self.client.reqCurrentTime()
|
||||
|
||||
self.thread.start()
|
||||
|
||||
n = self.client.reqCurrentTime()
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Disconnect to TWS.
|
||||
"""
|
||||
if not self.status:
|
||||
return
|
||||
|
||||
self.status = False
|
||||
self.client.eDisconnect()
|
||||
|
||||
def subscribe(self, req:SubscribeRequest):
|
||||
self.client.disconnect()
|
||||
|
||||
def subscribe(self, req: SubscribeRequest):
|
||||
"""
|
||||
Subscribe tick data update.
|
||||
"""
|
||||
if not self.status:
|
||||
return
|
||||
|
||||
ib_contract = Contract()
|
||||
ib_contract.conId = str(req.symbol)
|
||||
ib_contract.exchange = EXCHANGE_VT2IB[req.exchange]
|
||||
|
||||
|
||||
# Get contract data from TWS.
|
||||
self.reqid += 1
|
||||
self.client.reqContractDetails(self.reqid, ib_contract)
|
||||
@ -526,26 +562,30 @@ class IbApi(EWrapper):
|
||||
gateway_name=self.gateway_name
|
||||
)
|
||||
self.ticks[self.reqid] = tick
|
||||
|
||||
self.tick_exchange[self.reqid] = req.exchange
|
||||
|
||||
def send_order(self, req: OrderRequest):
|
||||
"""
|
||||
Send a new order.
|
||||
"""
|
||||
if not self.status:
|
||||
return ''
|
||||
|
||||
self.orderid += 1
|
||||
|
||||
ib_contract = Contract()
|
||||
ib_contract.conId = str(req.symbol)
|
||||
ib_contract.exchange = EXCHANGE_VT2IB[req.exchange]
|
||||
|
||||
order = Order()
|
||||
order.orderId = self.orderid
|
||||
order.clientId = self.clientid
|
||||
order.action = DIRECTION_VT2IB[req.direction]
|
||||
order.orderType = PRICETYPE_VT2IB[req.price_type]
|
||||
order.lmtPrice = req.price
|
||||
order.totalQuantity = req.volume
|
||||
ib_order = Order()
|
||||
ib_order.orderId = self.orderid
|
||||
ib_order.clientId = self.clientid
|
||||
ib_order.action = DIRECTION_VT2IB[req.direction]
|
||||
ib_order.orderType = PRICETYPE_VT2IB[req.price_type]
|
||||
ib_order.lmtPrice = req.price
|
||||
ib_order.totalQuantity = req.volume
|
||||
|
||||
self.client.placeOrder(self.orderid, contract, order)
|
||||
self.client.placeOrder(self.orderid, ib_contract, ib_order)
|
||||
self.client.reqIds(1)
|
||||
|
||||
vt_orderid = f"{self.gateway_name}.{self.orderid}"
|
||||
@ -555,7 +595,32 @@ class IbApi(EWrapper):
|
||||
"""
|
||||
Cancel an existing order.
|
||||
"""
|
||||
self.client.cancelOrder(int(req.orderid)))
|
||||
if not self.status:
|
||||
return
|
||||
|
||||
self.client.cancelOrder(int(req.orderid))
|
||||
|
||||
|
||||
class IbClient(EClient):
|
||||
""""""
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Reimplement the original run message loop of eclient.
|
||||
|
||||
Remove all unnecessary try...catch... and allow exceptions to interrupt loop.
|
||||
"""
|
||||
while not self.done and self.isConnected():
|
||||
try:
|
||||
text = self.msg_queue.get(block=True, timeout=0.2)
|
||||
|
||||
if len(text) > MAX_MSG_LEN:
|
||||
errorMsg = "%s:%d:%s" % (BAD_LENGTH.msg(), len(text), text)
|
||||
self.wrapper.error(NO_VALID_ID, BAD_LENGTH.code(), errorMsg)
|
||||
self.disconnect()
|
||||
break
|
||||
|
||||
fields = comm.read_fields(text)
|
||||
self.decoder.interpret(fields)
|
||||
except Empty:
|
||||
pass
|
||||
|
11
vnpy/trader/app.py
Normal file
11
vnpy/trader/app.py
Normal file
@ -0,0 +1,11 @@
|
||||
from abc import ABC
|
||||
|
||||
|
||||
class BaseApp(ABC):
|
||||
"""
|
||||
Abstract class for app.
|
||||
"""
|
||||
|
||||
app_name = ''
|
||||
app_engine = None
|
||||
app_ui = None
|
@ -18,7 +18,7 @@ from .event import (
|
||||
EVENT_CONTRACT
|
||||
)
|
||||
from .object import LogData, SubscribeRequest, OrderRequest, CancelRequest
|
||||
from .utility import Singleton, get_temp_path, check_order_active
|
||||
from .utility import Singleton, get_temp_path
|
||||
from .setting import SETTINGS
|
||||
from .gateway import BaseGateway
|
||||
|
||||
@ -54,7 +54,7 @@ class MainEngine:
|
||||
Add gateway.
|
||||
"""
|
||||
gateway = gateway_class(self.event_engine)
|
||||
self.gateways[gateway.gateway_name] = engine
|
||||
self.gateways[gateway.gateway_name] = gateway
|
||||
|
||||
def init_engines(self):
|
||||
"""
|
||||
@ -78,6 +78,7 @@ class MainEngine:
|
||||
gateway = self.gateways.get(gateway_name, None)
|
||||
if not gateway:
|
||||
self.write_log(f"找不到底层接口:{gateway_name}")
|
||||
return None
|
||||
return gateway
|
||||
|
||||
def get_default_setting(self, gateway_name: str):
|
||||
@ -87,14 +88,21 @@ class MainEngine:
|
||||
gateway = self.get_gateway(gateway_name)
|
||||
if gateway:
|
||||
return gateway.get_default_setting()
|
||||
return None
|
||||
|
||||
def connect(self, gateway_name: str):
|
||||
def get_all_gateway_names(self):
|
||||
"""
|
||||
Get all names of gatewasy added in main engine.
|
||||
"""
|
||||
return list(self.gateways.keys())
|
||||
|
||||
def connect(self, setting: dict, gateway_name: str):
|
||||
"""
|
||||
Start connection of a specific gateway.
|
||||
"""
|
||||
gateway = self.get_gateway(gateway_name)
|
||||
if gateway:
|
||||
gateway.connect()
|
||||
gateway.connect(setting)
|
||||
|
||||
def subscribe(self, req: SubscribeRequest, gateway_name: str):
|
||||
"""
|
||||
@ -120,7 +128,7 @@ class MainEngine:
|
||||
"""
|
||||
gateway = self.get_gateway(gateway_name)
|
||||
if gateway:
|
||||
gateway.send_order(req)
|
||||
gateway.cancel_order(req)
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
@ -277,7 +285,7 @@ class OmsEngine(BaseEngine):
|
||||
self.orders[order.vt_orderid] = order
|
||||
|
||||
# If order is active, then update data in dict.
|
||||
if check_order_active(order.status):
|
||||
if order.check_active():
|
||||
self.active_orders[order.vt_orderid] = order
|
||||
# Otherwise, pop inactive order from in dict
|
||||
elif order.vt_orderid in self.active_orders:
|
||||
|
@ -6,6 +6,10 @@ from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from logging import INFO
|
||||
|
||||
from .constant import (STATUS_SUBMITTING, STATUS_NOTTRADED, STATUS_PARTTRADED)
|
||||
|
||||
ACTIVE_STATUSES = set([STATUS_SUBMITTING, STATUS_NOTTRADED, STATUS_PARTTRADED])
|
||||
|
||||
|
||||
@dataclass
|
||||
class BaseData:
|
||||
@ -110,6 +114,26 @@ class OrderData(BaseData):
|
||||
self.vt_symbol = f"{self.symbol}.{self.exchange}"
|
||||
self.vt_orderid = f"{self.gateway_name}.{self.orderid}"
|
||||
|
||||
def check_active(self):
|
||||
"""
|
||||
Check if the order is active.
|
||||
"""
|
||||
if self.status in ACTIVE_STATUSES:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def create_cancel_request(self):
|
||||
"""
|
||||
Create cancel request object from order.
|
||||
"""
|
||||
req = CancelRequest(
|
||||
orderid=self.orderid,
|
||||
symbol=self.symbol,
|
||||
exchange=self.exchange
|
||||
)
|
||||
return req
|
||||
|
||||
|
||||
@dataclass
|
||||
class TradeData(BaseData):
|
||||
@ -182,7 +206,7 @@ class LogData(BaseData):
|
||||
|
||||
def __post_init__(self):
|
||||
""""""
|
||||
time = datetime
|
||||
self.time = datetime.now()
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -7,6 +7,7 @@ from PyQt5 import QtWidgets, QtGui
|
||||
|
||||
from .mainwindow import MainWindow
|
||||
from ..setting import SETTINGS
|
||||
from ..utility import get_icon_path
|
||||
|
||||
|
||||
def create_qapp():
|
||||
@ -19,9 +20,7 @@ def create_qapp():
|
||||
font = QtGui.QFont(SETTINGS["font.family"], SETTINGS["font.size"])
|
||||
qapp.setFont(font)
|
||||
|
||||
ui_path = Path(__file__).parent
|
||||
icon_path = ui_path.joinpath("ico", "vnpy.ico")
|
||||
icon = QtGui.QIcon(str(icon_path))
|
||||
icon = QtGui.QIcon(get_icon_path(__file__, "vnpy.ico"))
|
||||
qapp.setWindowIcon(icon)
|
||||
|
||||
if 'Windows' in platform.uname():
|
||||
|
@ -1,14 +1,23 @@
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
"""
|
||||
Implements main window of VN Trader.
|
||||
"""
|
||||
|
||||
from functools import partial
|
||||
|
||||
from PyQt5 import QtWidgets, QtCore, QtGui
|
||||
|
||||
from vnpy.event import EventEngine
|
||||
from ..engine import MainEngine
|
||||
from ..utility import get_icon_path
|
||||
from .widget import (
|
||||
TickMonitor,
|
||||
OrderMonitor,
|
||||
TradeMonitor,
|
||||
PositionMonitor,
|
||||
AccountMonitor,
|
||||
LogMonitor
|
||||
LogMonitor,
|
||||
ConnectDialog,
|
||||
TradingWidget
|
||||
)
|
||||
|
||||
|
||||
@ -23,6 +32,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.main_engine = main_engine
|
||||
self.event_engine = event_engine
|
||||
|
||||
self.connect_dialogs = {}
|
||||
self.widgets = {}
|
||||
|
||||
self.init_ui()
|
||||
@ -35,6 +45,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
|
||||
def init_dock(self):
|
||||
""""""
|
||||
trading_widget, trading_dock = self.create_dock(TradingWidget, "交易", QtCore.Qt.LeftDockWidgetArea)
|
||||
tick_widget, tick_dock = self.create_dock(TickMonitor, "行情", QtCore.Qt.RightDockWidgetArea)
|
||||
order_widget, order_dock = self.create_dock(OrderMonitor, "委托", QtCore.Qt.RightDockWidgetArea)
|
||||
trade_widget, trade_dock = self.create_dock(TradeMonitor, "成交", QtCore.Qt.RightDockWidgetArea)
|
||||
@ -44,7 +55,22 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
|
||||
def init_menu(self):
|
||||
""""""
|
||||
pass
|
||||
bar = self.menuBar()
|
||||
|
||||
sys_menu = bar.addMenu("系统")
|
||||
app_menu = bar.addMenu("功能")
|
||||
help_menu = bar.addMenu("帮助")
|
||||
|
||||
gateway_names = self.main_engine.get_all_gateway_names()
|
||||
for name in gateway_names:
|
||||
func = partial(self.connect, name)
|
||||
icon = QtGui.QIcon(get_icon_path(__file__, "connect.ico"))
|
||||
|
||||
action = QtWidgets.QAction(f"连接{name}", self)
|
||||
action.triggered.connect(func)
|
||||
action.setIcon(icon)
|
||||
|
||||
sys_menu.addAction(action)
|
||||
|
||||
def create_dock(
|
||||
self,
|
||||
@ -62,4 +88,36 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
dock.setObjectName(name)
|
||||
dock.setFeatures(dock.DockWidgetFloatable | dock.DockWidgetMovable)
|
||||
self.addDockWidget(area, dock)
|
||||
return widget, dock
|
||||
return widget, dock
|
||||
|
||||
def connect(self, gateway_name: str):
|
||||
"""
|
||||
Open connect dialog for gateway connection.
|
||||
"""
|
||||
dialog = self.connect_dialogs.get(gateway_name, None)
|
||||
if not dialog:
|
||||
dialog = ConnectDialog(self.main_engine, gateway_name)
|
||||
|
||||
dialog.exec()
|
||||
|
||||
def closeEvent(self, event):
|
||||
"""
|
||||
Call main engine close function before exit.
|
||||
"""
|
||||
reply = QtWidgets.QMessageBox.question(
|
||||
self,
|
||||
"退出",
|
||||
"确认退出?",
|
||||
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
|
||||
QtWidgets.QMessageBox.No
|
||||
)
|
||||
|
||||
if reply == QtWidgets.QMessageBox.Yes:
|
||||
for widget in self.widgets.values():
|
||||
widget.close()
|
||||
|
||||
self.main_engine.close()
|
||||
|
||||
event.accept()
|
||||
else:
|
||||
event.ignore()
|
||||
|
@ -8,10 +8,16 @@ 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 ..constant import (DIRECTION_LONG, DIRECTION_SHORT, DIRECTION_NET,
|
||||
OFFSET_OPEN, OFFSET_CLOSE, OFFSET_CLOSETODAY, OFFSET_CLOSEYESTERDAY,
|
||||
PRICETYPE_LIMIT, PRICETYPE_MARKET, PRICETYPE_FAK, PRICETYPE_FOK,
|
||||
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_GLOBEX, EXCHANGE_IDEALPRO)
|
||||
from ..engine import MainEngine
|
||||
from ..event import (EVENT_TICK, EVENT_ORDER, EVENT_TRADE, EVENT_ACCOUNT,
|
||||
EVENT_POSITION, EVENT_CONTRACT, EVENT_LOG)
|
||||
from ..object import SubscribeRequest, OrderRequest, CancelRequest
|
||||
|
||||
COLOR_LONG = QtGui.QColor("red")
|
||||
COLOR_SHORT = QtGui.QColor("green")
|
||||
@ -27,7 +33,7 @@ class BaseCell(QtWidgets.QTableWidgetItem):
|
||||
|
||||
def __init__(self, content: Any, data: Any):
|
||||
""""""
|
||||
super(BaseCel, self).__init__()
|
||||
super(BaseCell, self).__init__()
|
||||
self.setTextAlignment(QtCore.Qt.AlignCenter)
|
||||
self.set_content(content, data)
|
||||
|
||||
@ -108,7 +114,7 @@ class PnlCell(BaseCell):
|
||||
"""
|
||||
super(PnlCell, self).set_content(content, data)
|
||||
|
||||
if content.startswith("-"):
|
||||
if str(content).startswith("-"):
|
||||
self.setForeground(COLOR_SHORT)
|
||||
else:
|
||||
self.setForeground(COLOR_LONG)
|
||||
@ -171,7 +177,7 @@ class BaseMonitor(QtWidgets.QTableWidget):
|
||||
"""
|
||||
self.setColumnCount(len(self.headers))
|
||||
|
||||
labels = [d['display'] for d in self.headers.values()]
|
||||
labels = [d["display"] for d in self.headers.values()]
|
||||
self.setHorizontalHeaderLabels(labels)
|
||||
|
||||
self.verticalHeader().setVisible(False)
|
||||
@ -198,7 +204,7 @@ class BaseMonitor(QtWidgets.QTableWidget):
|
||||
Register event handler into event engine.
|
||||
"""
|
||||
self.signal.connect(self.process_event)
|
||||
self.event_engine.register(self.event_type, self.process_event)
|
||||
self.event_engine.register(self.event_type, self.signal.emit)
|
||||
|
||||
def process_event(self, event):
|
||||
"""
|
||||
@ -236,10 +242,10 @@ class BaseMonitor(QtWidgets.QTableWidget):
|
||||
setting = self.headers[header]
|
||||
|
||||
content = data.__getattribute__(header)
|
||||
cell = setting['cell'](content, data)
|
||||
cell = setting["cell"](content, data)
|
||||
self.setItem(0, column, cell)
|
||||
|
||||
if setting['update']:
|
||||
if setting["update"]:
|
||||
row_cells[header] = cell
|
||||
|
||||
if self.data_key:
|
||||
@ -253,7 +259,7 @@ class BaseMonitor(QtWidgets.QTableWidget):
|
||||
key = data.__getattribute__(self.data_key)
|
||||
row_cells = self.cells[key]
|
||||
|
||||
for header, cell in row_cells:
|
||||
for header, cell in row_cells.items():
|
||||
content = data.__getattribute__(header)
|
||||
cell.set_content(content, data)
|
||||
|
||||
@ -298,65 +304,65 @@ class TickMonitor(BaseMonitor):
|
||||
sorting = True
|
||||
|
||||
headers = {
|
||||
'symbol': {
|
||||
'display': '代码',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
"symbol": {
|
||||
"display": "代码",
|
||||
"cell": BaseCell,
|
||||
"update": False
|
||||
},
|
||||
'last_price': {
|
||||
'display': '最新价',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
"last_price": {
|
||||
"display": "最新价",
|
||||
"cell": BaseCell,
|
||||
"update": True
|
||||
},
|
||||
'volume': {
|
||||
'display': '成交量',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
"volume": {
|
||||
"display": "成交量",
|
||||
"cell": BaseCell,
|
||||
"update": True
|
||||
},
|
||||
'open_price': {
|
||||
'display': '开盘价',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
"open_price": {
|
||||
"display": "开盘价",
|
||||
"cell": BaseCell,
|
||||
"update": True
|
||||
},
|
||||
'high_price': {
|
||||
'display': '最高价',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
"high_price": {
|
||||
"display": "最高价",
|
||||
"cell": BaseCell,
|
||||
"update": True
|
||||
},
|
||||
'low_price': {
|
||||
'display': '最低价',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
"low_price": {
|
||||
"display": "最低价",
|
||||
"cell": BaseCell,
|
||||
"update": True
|
||||
},
|
||||
'bid_price_1': {
|
||||
'display': '买1价',
|
||||
'cell': BidCell,
|
||||
'update': True
|
||||
"bid_price_1": {
|
||||
"display": "买1价",
|
||||
"cell": BidCell,
|
||||
"update": True
|
||||
},
|
||||
'bid_volume_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_price_1": {
|
||||
"display": "卖1价",
|
||||
"cell": AskCell,
|
||||
"update": True
|
||||
},
|
||||
'ask_volume_1': {
|
||||
'display': '卖1量',
|
||||
'cell': AskCell,
|
||||
'update': True
|
||||
"ask_volume_1": {
|
||||
"display": "卖1量",
|
||||
"cell": AskCell,
|
||||
"update": True
|
||||
},
|
||||
'time': {
|
||||
'display': '时间',
|
||||
'cell': TimeCell,
|
||||
'update': True
|
||||
"datetime": {
|
||||
"display": "时间",
|
||||
"cell": TimeCell,
|
||||
"update": True
|
||||
},
|
||||
'gateway_name': {
|
||||
'display': '接口',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
"gateway_name": {
|
||||
"display": "接口",
|
||||
"cell": BaseCell,
|
||||
"update": False
|
||||
}
|
||||
}
|
||||
|
||||
@ -370,28 +376,23 @@ class LogMonitor(BaseMonitor):
|
||||
sorting = False
|
||||
|
||||
headers = {
|
||||
'time': {
|
||||
'display': '时间',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
"time": {
|
||||
"display": "时间",
|
||||
"cell": TimeCell,
|
||||
"update": False
|
||||
},
|
||||
'msg': {
|
||||
'display': '信息',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
"msg": {
|
||||
"display": "信息",
|
||||
"cell": BaseCell,
|
||||
"update": False
|
||||
},
|
||||
'gateway_name': {
|
||||
'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):
|
||||
"""
|
||||
@ -402,45 +403,45 @@ class TradeMonitor(BaseMonitor):
|
||||
sorting = True
|
||||
|
||||
headers = {
|
||||
'tradeid': {
|
||||
'display': "成交号 ",
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
"tradeid": {
|
||||
"display": "成交号 ",
|
||||
"cell": BaseCell,
|
||||
"update": False
|
||||
},
|
||||
'orderid': {
|
||||
'display': '委托号',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
"orderid": {
|
||||
"display": "委托号",
|
||||
"cell": BaseCell,
|
||||
"update": False
|
||||
},
|
||||
'symbol': {
|
||||
'display': '代码',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
"symbol": {
|
||||
"display": "代码",
|
||||
"cell": BaseCell,
|
||||
"update": False
|
||||
},
|
||||
'direction': {
|
||||
'display': '方向',
|
||||
'cell': DirectionCell,
|
||||
'update': False
|
||||
"direction": {
|
||||
"display": "方向",
|
||||
"cell": DirectionCell,
|
||||
"update": False
|
||||
},
|
||||
'offset': {
|
||||
'display': '开平',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
"offset": {
|
||||
"display": "开平",
|
||||
"cell": BaseCell,
|
||||
"update": False
|
||||
},
|
||||
'price': {
|
||||
'display': '价格',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
"price": {
|
||||
"display": "价格",
|
||||
"cell": BaseCell,
|
||||
"update": False
|
||||
},
|
||||
'volume': {
|
||||
'display': '数量',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
"volume": {
|
||||
"display": "数量",
|
||||
"cell": BaseCell,
|
||||
"update": False
|
||||
},
|
||||
'gateway_name': {
|
||||
'display': '接口',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
"gateway_name": {
|
||||
"display": "接口",
|
||||
"cell": BaseCell,
|
||||
"update": False
|
||||
}
|
||||
}
|
||||
|
||||
@ -454,55 +455,55 @@ class OrderMonitor(BaseMonitor):
|
||||
sorting = True
|
||||
|
||||
headers = {
|
||||
'orderid': {
|
||||
'display': '委托号',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
"orderid": {
|
||||
"display": "委托号",
|
||||
"cell": BaseCell,
|
||||
"update": False
|
||||
},
|
||||
'symbol': {
|
||||
'display': '代码',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
"symbol": {
|
||||
"display": "代码",
|
||||
"cell": BaseCell,
|
||||
"update": False
|
||||
},
|
||||
'direction': {
|
||||
'display': '方向',
|
||||
'cell': DirectionCell,
|
||||
'update': False
|
||||
"direction": {
|
||||
"display": "方向",
|
||||
"cell": DirectionCell,
|
||||
"update": False
|
||||
},
|
||||
'offset': {
|
||||
'display': '开平',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
"offset": {
|
||||
"display": "开平",
|
||||
"cell": BaseCell,
|
||||
"update": False
|
||||
},
|
||||
'price': {
|
||||
'display': '价格',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
"price": {
|
||||
"display": "价格",
|
||||
"cell": BaseCell,
|
||||
"update": False
|
||||
},
|
||||
'volume': {
|
||||
'display': '总数量',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
"volume": {
|
||||
"display": "总数量",
|
||||
"cell": BaseCell,
|
||||
"update": True
|
||||
},
|
||||
'traded': {
|
||||
'display': '已成交',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
"traded": {
|
||||
"display": "已成交",
|
||||
"cell": BaseCell,
|
||||
"update": True
|
||||
},
|
||||
'status': {
|
||||
'display': '状态',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
"status": {
|
||||
"display": "状态",
|
||||
"cell": BaseCell,
|
||||
"update": True
|
||||
},
|
||||
'time': {
|
||||
'display': '时间',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
"time": {
|
||||
"display": "时间",
|
||||
"cell": BaseCell,
|
||||
"update": True
|
||||
},
|
||||
'gateway_name': {
|
||||
'display': '接口',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
"gateway_name": {
|
||||
"display": "接口",
|
||||
"cell": BaseCell,
|
||||
"update": False
|
||||
}
|
||||
}
|
||||
|
||||
@ -515,40 +516,40 @@ class PositionMonitor(BaseMonitor):
|
||||
sorting = True
|
||||
|
||||
headers = {
|
||||
'symbol': {
|
||||
'display': '代码',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
"symbol": {
|
||||
"display": "代码",
|
||||
"cell": BaseCell,
|
||||
"update": False
|
||||
},
|
||||
'direction': {
|
||||
'display': '方向',
|
||||
'cell': DirectionCell,
|
||||
'update': False
|
||||
"direction": {
|
||||
"display": "方向",
|
||||
"cell": DirectionCell,
|
||||
"update": False
|
||||
},
|
||||
'volume': {
|
||||
'display': '数量',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
"volume": {
|
||||
"display": "数量",
|
||||
"cell": BaseCell,
|
||||
"update": True
|
||||
},
|
||||
'frozen': {
|
||||
'display': '冻结',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
"frozen": {
|
||||
"display": "冻结",
|
||||
"cell": BaseCell,
|
||||
"update": True
|
||||
},
|
||||
'price': {
|
||||
'display': '均价',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
"price": {
|
||||
"display": "均价",
|
||||
"cell": BaseCell,
|
||||
"update": False
|
||||
},
|
||||
'pnl': {
|
||||
'display': '盈亏',
|
||||
'cell': PnlCell,
|
||||
'update': True
|
||||
"pnl": {
|
||||
"display": "盈亏",
|
||||
"cell": PnlCell,
|
||||
"update": True
|
||||
},
|
||||
'gateway_name': {
|
||||
'display': '接口',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
"gateway_name": {
|
||||
"display": "接口",
|
||||
"cell": BaseCell,
|
||||
"update": False
|
||||
}
|
||||
}
|
||||
|
||||
@ -562,29 +563,399 @@ class AccountMonitor(BaseMonitor):
|
||||
sorting = True
|
||||
|
||||
headers = {
|
||||
'accountid': {
|
||||
'display': '账号',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
"accountid": {
|
||||
"display": "账号",
|
||||
"cell": BaseCell,
|
||||
"update": False
|
||||
},
|
||||
'balance': {
|
||||
'display': '余额',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
"balance": {
|
||||
"display": "余额",
|
||||
"cell": BaseCell,
|
||||
"update": True
|
||||
},
|
||||
'frozen': {
|
||||
'display': '冻结',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
"frozen": {
|
||||
"display": "冻结",
|
||||
"cell": BaseCell,
|
||||
"update": True
|
||||
},
|
||||
'available': {
|
||||
'display': '可用',
|
||||
'cell': BaseCell,
|
||||
'update': True
|
||||
"available": {
|
||||
"display": "可用",
|
||||
"cell": BaseCell,
|
||||
"update": True
|
||||
},
|
||||
'gateway_name': {
|
||||
'display': '接口',
|
||||
'cell': BaseCell,
|
||||
'update': False
|
||||
"gateway_name": {
|
||||
"display": "接口",
|
||||
"cell": BaseCell,
|
||||
"update": False
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ConnectDialog(QtWidgets.QDialog):
|
||||
"""
|
||||
Start connection of a certain gateway.
|
||||
"""
|
||||
|
||||
def __init__(self, main_engine: MainEngine, gateway_name: str):
|
||||
""""""
|
||||
super(ConnectDialog, self).__init__()
|
||||
|
||||
self.main_engine = main_engine
|
||||
self.gateway_name = gateway_name
|
||||
|
||||
self.line_edits = {}
|
||||
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
""""""
|
||||
self.setWindowTitle(f"连接{self.gateway_name}")
|
||||
|
||||
form = QtWidgets.QFormLayout()
|
||||
default_setting = self.main_engine.get_default_setting(self.gateway_name)
|
||||
|
||||
# Initialize line edits and form layout based on default setting.
|
||||
for field_name, field_value in default_setting.items():
|
||||
field_type = type(field_value)
|
||||
line_edit = QtWidgets.QLineEdit(str(field_value))
|
||||
|
||||
form.addRow(f"{field_name} <{field_type.__name__}>", line_edit)
|
||||
self.line_edits[field_name] = (line_edit, field_type)
|
||||
|
||||
button = QtWidgets.QPushButton(u"连接")
|
||||
button.clicked.connect(self.connect)
|
||||
form.addRow(button)
|
||||
|
||||
self.setLayout(form)
|
||||
|
||||
def connect(self):
|
||||
"""
|
||||
Get setting value from line edits and connect the gateway.
|
||||
"""
|
||||
setting = {}
|
||||
for field_name, tp in self.line_edits.items():
|
||||
line_edit, field_type = tp
|
||||
field_value = field_type(line_edit.text())
|
||||
setting[field_name] = field_value
|
||||
|
||||
self.main_engine.connect(setting, self.gateway_name)
|
||||
self.accept()
|
||||
|
||||
|
||||
class TradingWidget(QtWidgets.QWidget):
|
||||
"""
|
||||
General manual trading widget.
|
||||
"""
|
||||
|
||||
signal_tick = QtCore.pyqtSignal(Event)
|
||||
|
||||
def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
|
||||
""""""
|
||||
super(TradingWidget, self).__init__()
|
||||
|
||||
self.main_engine = main_engine
|
||||
self.event_engine = event_engine
|
||||
|
||||
self.vt_symbol = ""
|
||||
|
||||
self.init_ui()
|
||||
self.register_event()
|
||||
|
||||
def init_ui(self):
|
||||
""""""
|
||||
self.setFixedWidth(300)
|
||||
|
||||
# Trading function area
|
||||
self.exchange_combo = QtWidgets.QComboBox()
|
||||
self.exchange_combo.addItems([
|
||||
EXCHANGE_CFFEX,
|
||||
EXCHANGE_SHFE,
|
||||
EXCHANGE_DCE,
|
||||
EXCHANGE_CZCE,
|
||||
EXCHANGE_SSE,
|
||||
EXCHANGE_SZSE,
|
||||
EXCHANGE_HKEX,
|
||||
EXCHANGE_HKFE,
|
||||
EXCHANGE_SMART,
|
||||
EXCHANGE_ICE,
|
||||
EXCHANGE_CME,
|
||||
EXCHANGE_NYMEX,
|
||||
EXCHANGE_GLOBEX,
|
||||
EXCHANGE_IDEALPRO
|
||||
])
|
||||
|
||||
self.symbol_line = QtWidgets.QLineEdit()
|
||||
self.symbol_line.returnPressed.connect(self.set_vt_symbol)
|
||||
|
||||
self.name_line = QtWidgets.QLineEdit()
|
||||
self.name_line.setReadOnly(True)
|
||||
|
||||
self.direction_combo = QtWidgets.QComboBox()
|
||||
self.direction_combo.addItems([
|
||||
DIRECTION_LONG,
|
||||
DIRECTION_SHORT
|
||||
])
|
||||
|
||||
self.offset_combo = QtWidgets.QComboBox()
|
||||
self.offset_combo.addItems([
|
||||
OFFSET_OPEN,
|
||||
OFFSET_CLOSE,
|
||||
OFFSET_CLOSETODAY,
|
||||
OFFSET_CLOSEYESTERDAY
|
||||
])
|
||||
|
||||
self.pricetype_combo = QtWidgets.QComboBox()
|
||||
self.pricetype_combo.addItems([
|
||||
PRICETYPE_LIMIT,
|
||||
PRICETYPE_MARKET,
|
||||
PRICETYPE_FAK,
|
||||
PRICETYPE_FOK
|
||||
])
|
||||
|
||||
double_validator = QtGui.QDoubleValidator()
|
||||
double_validator.setBottom(0)
|
||||
|
||||
self.price_line = QtWidgets.QLineEdit()
|
||||
self.price_line.setValidator(double_validator)
|
||||
|
||||
self.volume_line = QtWidgets.QLineEdit()
|
||||
self.volume_line.setValidator(double_validator)
|
||||
|
||||
self.gateway_combo = QtWidgets.QComboBox()
|
||||
self.gateway_combo.addItems(self.main_engine.get_all_gateway_names())
|
||||
|
||||
send_button = QtWidgets.QPushButton("委托")
|
||||
send_button.clicked.connect(self.send_order)
|
||||
|
||||
cancel_button = QtWidgets.QPushButton("全撤")
|
||||
cancel_button.clicked.connect(self.cancel_all)
|
||||
|
||||
form1 = QtWidgets.QFormLayout()
|
||||
form1.addRow("交易所", self.exchange_combo)
|
||||
form1.addRow("代码", self.symbol_line)
|
||||
form1.addRow("名称", self.name_line)
|
||||
form1.addRow("方向", self.direction_combo)
|
||||
form1.addRow("开平", self.offset_combo)
|
||||
form1.addRow("类型", self.pricetype_combo)
|
||||
form1.addRow("价格", self.price_line)
|
||||
form1.addRow("数量", self.volume_line)
|
||||
form1.addRow("接口", self.gateway_combo)
|
||||
form1.addRow(send_button)
|
||||
form1.addRow(cancel_button)
|
||||
|
||||
# Market depth display area
|
||||
bid_color = "rgb(255,174,201)"
|
||||
ask_color = "rgb(160,255,160)"
|
||||
|
||||
self.bp1_label = self.create_label(bid_color)
|
||||
self.bp2_label = self.create_label(bid_color)
|
||||
self.bp3_label = self.create_label(bid_color)
|
||||
self.bp4_label = self.create_label(bid_color)
|
||||
self.bp5_label = self.create_label(bid_color)
|
||||
|
||||
self.bv1_label = self.create_label(bid_color, alignment=QtCore.Qt.AlignRight)
|
||||
self.bv2_label = self.create_label(bid_color, alignment=QtCore.Qt.AlignRight)
|
||||
self.bv3_label = self.create_label(bid_color, alignment=QtCore.Qt.AlignRight)
|
||||
self.bv4_label = self.create_label(bid_color, alignment=QtCore.Qt.AlignRight)
|
||||
self.bv5_label = self.create_label(bid_color, alignment=QtCore.Qt.AlignRight)
|
||||
|
||||
self.ap1_label = self.create_label(ask_color)
|
||||
self.ap2_label = self.create_label(ask_color)
|
||||
self.ap3_label = self.create_label(ask_color)
|
||||
self.ap4_label = self.create_label(ask_color)
|
||||
self.ap5_label = self.create_label(ask_color)
|
||||
|
||||
self.av1_label = self.create_label(ask_color, alignment=QtCore.Qt.AlignRight)
|
||||
self.av2_label = self.create_label(ask_color, alignment=QtCore.Qt.AlignRight)
|
||||
self.av3_label = self.create_label(ask_color, alignment=QtCore.Qt.AlignRight)
|
||||
self.av4_label = self.create_label(ask_color, alignment=QtCore.Qt.AlignRight)
|
||||
self.av5_label = self.create_label(ask_color, alignment=QtCore.Qt.AlignRight)
|
||||
|
||||
self.lp_label = self.create_label()
|
||||
self.return_label = self.create_label(alignment=QtCore.Qt.AlignRight)
|
||||
|
||||
form2 = QtWidgets.QFormLayout()
|
||||
form2.addRow(self.ap5_label, self.av5_label)
|
||||
form2.addRow(self.ap4_label, self.av4_label)
|
||||
form2.addRow(self.ap3_label, self.av3_label)
|
||||
form2.addRow(self.ap2_label, self.av2_label)
|
||||
form2.addRow(self.ap1_label, self.av1_label)
|
||||
form2.addRow(self.lp_label, self.return_label)
|
||||
form2.addRow(self.bp1_label, self.bv1_label)
|
||||
form2.addRow(self.bp2_label, self.bv2_label)
|
||||
form2.addRow(self.bp3_label, self.bv3_label)
|
||||
form2.addRow(self.bp4_label, self.bv4_label)
|
||||
form2.addRow(self.bp5_label, self.bv5_label)
|
||||
|
||||
# Overall layout
|
||||
vbox = QtWidgets.QVBoxLayout()
|
||||
vbox.addLayout(form1)
|
||||
vbox.addLayout(form2)
|
||||
self.setLayout(vbox)
|
||||
|
||||
def create_label(self, color: str = "", alignment: int = QtCore.Qt.AlignLeft):
|
||||
"""
|
||||
Create label with certain font color.
|
||||
"""
|
||||
label = QtWidgets.QLabel()
|
||||
if color:
|
||||
label.setStyleSheet(f"color:{color}")
|
||||
label.setAlignment(alignment)
|
||||
return label
|
||||
|
||||
def register_event(self):
|
||||
""""""
|
||||
self.signal_tick.connect(self.process_tick_event)
|
||||
self.event_engine.register(EVENT_TICK, self.signal_tick.emit)
|
||||
|
||||
def process_tick_event(self, event: Event):
|
||||
""""""
|
||||
tick = event.data
|
||||
if tick.vt_symbol != self.vt_symbol:
|
||||
return
|
||||
|
||||
self.lp_label.setText(str(tick.last_price))
|
||||
self.bp1_label.setText(str(tick.bid_price_1))
|
||||
self.bv1_label.setText(str(tick.bid_volume_1))
|
||||
self.ap1_label.setText(str(tick.ask_price_1))
|
||||
self.av1_label.setText(str(tick.ask_volume_1))
|
||||
|
||||
if tick.pre_close:
|
||||
r = (tick.last_price / tick.pre_close - 1) * 100
|
||||
self.return_label.setText(f"{r:.2f}%")
|
||||
|
||||
if tick.bid_price_2:
|
||||
self.bp2_label.setText(str(tick.bid_price_2))
|
||||
self.bv2_label.setText(str(tick.bid_volume_2))
|
||||
self.ap2_label.setText(str(tick.ask_price_2))
|
||||
self.av2_label.setText(str(tick.ask_volume_2))
|
||||
|
||||
self.bp3_label.setText(str(tick.bid_price_3))
|
||||
self.bv3_label.setText(str(tick.bid_volume_3))
|
||||
self.ap3_label.setText(str(tick.ask_price_3))
|
||||
self.av3_label.setText(str(tick.ask_volume_3))
|
||||
|
||||
self.bp4_label.setText(str(tick.bid_price_4))
|
||||
self.bv4_label.setText(str(tick.bid_volume_4))
|
||||
self.ap4_label.setText(str(tick.ask_price_4))
|
||||
self.av4_label.setText(str(tick.ask_volume_4))
|
||||
|
||||
self.bp5_label.setText(str(tick.bid_price_5))
|
||||
self.bv5_label.setText(str(tick.bid_volume_5))
|
||||
self.ap5_label.setText(str(tick.ask_price_5))
|
||||
self.av5_label.setText(str(tick.ask_volume_5))
|
||||
|
||||
def set_vt_symbol(self):
|
||||
"""
|
||||
Set the tick depth data to monitor by vt_symbol.
|
||||
"""
|
||||
symbol = str(self.symbol_line.text())
|
||||
if not symbol:
|
||||
return
|
||||
|
||||
# Generate vt_symbol from symbol and exchange
|
||||
exchange = str(self.exchange_combo.currentText())
|
||||
vt_symbol = f"{symbol}.{exchange}"
|
||||
|
||||
if vt_symbol == self.vt_symbol:
|
||||
return
|
||||
self.vt_symbol = vt_symbol
|
||||
|
||||
# Update name line widget and clear all labels
|
||||
contract = self.main_engine.get_contract(vt_symbol)
|
||||
if not contract:
|
||||
self.name_line.setText("")
|
||||
else:
|
||||
self.name_line.setText(contract.name)
|
||||
|
||||
self.clear_label_text()
|
||||
|
||||
# Subscribe tick data
|
||||
req = SubscribeRequest(
|
||||
symbol=symbol,
|
||||
exchange=exchange
|
||||
)
|
||||
gateway_name = (self.gateway_combo.currentText())
|
||||
|
||||
self.main_engine.subscribe(req, gateway_name)
|
||||
|
||||
def clear_label_text(self):
|
||||
"""
|
||||
Clear text on all labels.
|
||||
"""
|
||||
self.lp_label.setText("")
|
||||
self.return_label.setText("")
|
||||
|
||||
self.bv1_label.setText("")
|
||||
self.bv2_label.setText("")
|
||||
self.bv3_label.setText("")
|
||||
self.bv4_label.setText("")
|
||||
self.bv5_label.setText("")
|
||||
|
||||
self.av1_label.setText("")
|
||||
self.av2_label.setText("")
|
||||
self.av3_label.setText("")
|
||||
self.av4_label.setText("")
|
||||
self.av5_label.setText("")
|
||||
|
||||
self.bp1_label.setText("")
|
||||
self.bp2_label.setText("")
|
||||
self.bp3_label.setText("")
|
||||
self.bp4_label.setText("")
|
||||
self.bp5_label.setText("")
|
||||
|
||||
self.ap1_label.setText("")
|
||||
self.ap2_label.setText("")
|
||||
self.ap3_label.setText("")
|
||||
self.ap4_label.setText("")
|
||||
self.ap5_label.setText("")
|
||||
|
||||
def send_order(self):
|
||||
"""
|
||||
Send new order manually.
|
||||
"""
|
||||
symbol = str(self.symbol_line.text())
|
||||
if not symbol:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
"委托失败",
|
||||
"请输入合约代码"
|
||||
)
|
||||
return
|
||||
|
||||
volume_text = str(self.volume_line.text())
|
||||
if not volume_text:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
"委托失败",
|
||||
"请输入委托数量"
|
||||
)
|
||||
return
|
||||
volume = float(volume_text)
|
||||
|
||||
price_text = str(self.price_line.text())
|
||||
if not price_text:
|
||||
price = 0
|
||||
else:
|
||||
price = float(price_text)
|
||||
|
||||
req = OrderRequest(
|
||||
symbol=symbol,
|
||||
exchange=str(self.exchange_combo.currentText()),
|
||||
direction=str(self.direction_combo.currentText()),
|
||||
price_type=str(self.pricetype_combo.currentText()),
|
||||
volume=volume,
|
||||
price=price,
|
||||
offset=str(self.offset_combo.currentText())
|
||||
)
|
||||
|
||||
gateway_name = str(self.gateway_combo.currentText())
|
||||
|
||||
self.main_engine.send_order(req, gateway_name)
|
||||
|
||||
def cancel_all(self):
|
||||
"""
|
||||
Cancel all active orders.
|
||||
"""
|
||||
order_list = self.main_engine.get_all_active_orders()
|
||||
for order in order_list:
|
||||
req = order.create_cancel_request()
|
||||
self.main_engine.cancel_order(req, order.gateway_name)
|
@ -40,14 +40,10 @@ def get_temp_path(filename: str):
|
||||
return temp_path.joinpath(filename)
|
||||
|
||||
|
||||
ACTIVE_STATUSES = set([STATUS_SUBMITTING, STATUS_NOTTRADED, STATUS_PARTTRADED])
|
||||
|
||||
|
||||
def check_order_active(status: str):
|
||||
def get_icon_path(file_path: str, ico_name: str):
|
||||
"""
|
||||
Check if order is active by status.
|
||||
Get path for icon file with ico name.
|
||||
"""
|
||||
if status in ACTIVE_STATUSES:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
ui_path = Path(file_path).parent
|
||||
icon_path = ui_path.joinpath("ico", ico_name)
|
||||
return str(icon_path)
|
||||
|
Loading…
Reference in New Issue
Block a user