Files
2026-07-13 12:07:23 +08:00

348 lines
11 KiB
Python

"""
Implements main window of the trading platform.
"""
from types import ModuleType
import webbrowser
from functools import partial
from importlib import import_module
from typing import TypeVar, cast
from collections.abc import Callable
import vnpy
from vnpy.event import EventEngine
from .qt import QtCore, QtGui, QtWidgets
from .widget import (
BaseMonitor,
TickMonitor,
OrderMonitor,
TradeMonitor,
PositionMonitor,
AccountMonitor,
LogMonitor,
ActiveOrderMonitor,
ConnectDialog,
ContractManager,
TradingWidget,
AboutDialog,
GlobalDialog,
WechatDialog
)
from ..engine import MainEngine, BaseApp, EmailEngine
from ..utility import get_icon_path, TRADER_DIR
from ..locale import _
WidgetType = TypeVar("WidgetType", bound="QtWidgets.QWidget")
class MainWindow(QtWidgets.QMainWindow):
"""
Main window of the trading platform.
"""
def __init__(self, main_engine: MainEngine, event_engine: EventEngine) -> None:
""""""
super().__init__()
self.main_engine: MainEngine = main_engine
self.event_engine: EventEngine = event_engine
self.window_title: str = _("VeighNa Trader 社区版 - {} [{}]").format(vnpy.__version__, TRADER_DIR)
self.widgets: dict[str, QtWidgets.QWidget] = {}
self.monitors: dict[str, BaseMonitor] = {}
self.init_ui()
def init_ui(self) -> None:
""""""
self.setWindowTitle(self.window_title)
self.init_dock()
self.init_toolbar()
self.init_menu()
self.load_window_setting("custom")
def init_dock(self) -> None:
""""""
self.trading_widget, trading_dock = self.create_dock(
TradingWidget, _("交易"), QtCore.Qt.DockWidgetArea.LeftDockWidgetArea
)
tick_widget, tick_dock = self.create_dock(
TickMonitor, _("行情"), QtCore.Qt.DockWidgetArea.RightDockWidgetArea
)
order_widget, order_dock = self.create_dock(
OrderMonitor, _("委托"), QtCore.Qt.DockWidgetArea.RightDockWidgetArea
)
active_widget, active_dock = self.create_dock(
ActiveOrderMonitor, _("活动"), QtCore.Qt.DockWidgetArea.RightDockWidgetArea
)
trade_widget, trade_dock = self.create_dock(
TradeMonitor, _("成交"), QtCore.Qt.DockWidgetArea.RightDockWidgetArea
)
log_widget, log_dock = self.create_dock(
LogMonitor, _("日志"), QtCore.Qt.DockWidgetArea.BottomDockWidgetArea
)
account_widget, account_dock = self.create_dock(
AccountMonitor, _("资金"), QtCore.Qt.DockWidgetArea.BottomDockWidgetArea
)
position_widget, position_dock = self.create_dock(
PositionMonitor, _("持仓"), QtCore.Qt.DockWidgetArea.BottomDockWidgetArea
)
self.tabifyDockWidget(active_dock, order_dock)
self.save_window_setting("default")
tick_widget.itemDoubleClicked.connect(self.trading_widget.update_with_cell)
position_widget.itemDoubleClicked.connect(self.trading_widget.update_with_cell)
def init_menu(self) -> None:
""""""
bar: QtWidgets.QMenuBar = self.menuBar()
bar.setNativeMenuBar(False) # for mac and linux
# System menu
sys_menu: QtWidgets.QMenu = bar.addMenu(_("系统"))
gateway_names: list = self.main_engine.get_all_gateway_names()
for name in gateway_names:
func: Callable = partial(self.connect_gateway, name)
self.add_action(
sys_menu,
_("连接{}").format(name),
get_icon_path(__file__, "connect.ico"),
func
)
sys_menu.addSeparator()
self.add_action(
sys_menu,
_("退出"),
get_icon_path(__file__, "exit.ico"),
self.close
)
# App menu
app_menu: QtWidgets.QMenu = bar.addMenu(_("功能"))
all_apps: list[BaseApp] = self.main_engine.get_all_apps()
for app in all_apps:
ui_module: ModuleType = import_module(app.app_module + ".ui")
widget_class: type[QtWidgets.QWidget] = getattr(ui_module, app.widget_name)
func = partial(self.open_widget, widget_class, app.app_name)
self.add_action(app_menu, app.display_name, app.icon_name, func, True)
# Global setting editor
setting_action: QtGui.QAction = QtGui.QAction(_("配置"), self)
setting_action.triggered.connect(self.edit_global_setting)
bar.addAction(setting_action)
# Wechat notification
wechat_action: QtGui.QAction = QtGui.QAction(_("微信"), self)
wechat_action.triggered.connect(self.open_wechat_dialog)
bar.addAction(wechat_action)
# Help menu
help_menu: QtWidgets.QMenu = bar.addMenu(_("帮助"))
self.add_action(
help_menu,
_("查询合约"),
get_icon_path(__file__, "contract.ico"),
partial(self.open_widget, ContractManager, "contract"),
True
)
self.add_action(
help_menu,
_("还原窗口"),
get_icon_path(__file__, "restore.ico"),
self.restore_window_setting
)
self.add_action(
help_menu,
_("测试邮件"),
get_icon_path(__file__, "email.ico"),
self.send_test_email
)
self.add_action(
help_menu,
_("社区论坛"),
get_icon_path(__file__, "forum.ico"),
self.open_forum,
True
)
self.add_action(
help_menu,
_("关于"),
get_icon_path(__file__, "about.ico"),
partial(self.open_widget, AboutDialog, "about"),
)
def init_toolbar(self) -> None:
""""""
self.toolbar: QtWidgets.QToolBar = QtWidgets.QToolBar(self)
self.toolbar.setObjectName(_("工具栏"))
self.toolbar.setFloatable(False)
self.toolbar.setMovable(False)
# Set button size
w: int = 40
size = QtCore.QSize(w, w)
self.toolbar.setIconSize(size)
# Set button spacing
layout: QtWidgets.QLayout | None = self.toolbar.layout()
if layout:
layout.setSpacing(10)
self.addToolBar(QtCore.Qt.ToolBarArea.LeftToolBarArea, self.toolbar)
def add_action(
self,
menu: QtWidgets.QMenu,
action_name: str,
icon_name: str,
func: Callable,
toolbar: bool = False
) -> None:
""""""
icon: QtGui.QIcon = QtGui.QIcon(icon_name)
action: QtGui.QAction = QtGui.QAction(action_name, self)
action.triggered.connect(func)
action.setIcon(icon)
menu.addAction(action)
if toolbar:
self.toolbar.addAction(action)
def create_dock(
self,
widget_class: type[WidgetType],
name: str,
area: QtCore.Qt.DockWidgetArea
) -> tuple[WidgetType, QtWidgets.QDockWidget]:
"""
Initialize a dock widget.
"""
widget: WidgetType = widget_class(self.main_engine, self.event_engine) # type: ignore
if isinstance(widget, BaseMonitor):
self.monitors[name] = widget
dock: QtWidgets.QDockWidget = QtWidgets.QDockWidget(name)
dock.setWidget(widget)
dock.setObjectName(name)
dock.setFeatures(dock.DockWidgetFeature.DockWidgetFloatable | dock.DockWidgetFeature.DockWidgetMovable)
self.addDockWidget(area, dock)
return widget, dock
def connect_gateway(self, gateway_name: str) -> None:
"""
Open connect dialog for gateway connection.
"""
dialog: ConnectDialog = ConnectDialog(self.main_engine, gateway_name)
dialog.exec()
def closeEvent(self, event: QtGui.QCloseEvent) -> None:
"""
Call main engine close function before exit.
"""
reply = QtWidgets.QMessageBox.question(
self,
_("退出"),
_("确认退出?"),
QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No,
QtWidgets.QMessageBox.StandardButton.No,
)
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
for widget in self.widgets.values():
widget.close()
for monitor in self.monitors.values():
monitor.save_setting()
self.save_window_setting("custom")
self.main_engine.close()
event.accept()
else:
event.ignore()
def open_widget(self, widget_class: type[QtWidgets.QWidget], name: str) -> None:
"""
Open contract manager.
"""
widget: QtWidgets.QWidget | None = self.widgets.get(name, None)
if not widget:
widget = widget_class(self.main_engine, self.event_engine) # type: ignore
self.widgets[name] = widget
if isinstance(widget, QtWidgets.QDialog):
widget.exec()
else:
widget.show()
def save_window_setting(self, name: str) -> None:
"""
Save current window size and state by trader path and setting name.
"""
settings: QtCore.QSettings = QtCore.QSettings(self.window_title, name)
settings.setValue("state", self.saveState())
settings.setValue("geometry", self.saveGeometry())
def load_window_setting(self, name: str) -> None:
"""
Load previous window size and state by trader path and setting name.
"""
settings: QtCore.QSettings = QtCore.QSettings(self.window_title, name)
state = settings.value("state")
geometry = settings.value("geometry")
if isinstance(state, QtCore.QByteArray):
self.restoreState(state)
self.restoreGeometry(geometry)
def restore_window_setting(self) -> None:
"""
Restore window to default setting.
"""
self.load_window_setting("default")
self.showMaximized()
def send_test_email(self) -> None:
"""
Sending a test email.
"""
email_engine: EmailEngine = cast(EmailEngine, self.main_engine.get_engine("email"))
email_engine.send_email("VeighNa Trader", "testing")
def open_forum(self) -> None:
"""
"""
webbrowser.open("https://www.vnpy.com/forum/")
def edit_global_setting(self) -> None:
"""
"""
dialog: GlobalDialog = GlobalDialog()
dialog.exec()
def open_wechat_dialog(self) -> None:
"""
Open WeChat notification dialog.
"""
dialog: WechatDialog = WechatDialog(self.main_engine, self.event_engine)
dialog.exec()