diff --git a/vnpy/app/portfolio_manager/engine.py b/vnpy/app/portfolio_manager/engine.py index d5dbaddf..0d166b4c 100644 --- a/vnpy/app/portfolio_manager/engine.py +++ b/vnpy/app/portfolio_manager/engine.py @@ -5,11 +5,11 @@ from copy import copy from vnpy.event import Event, EventEngine from vnpy.trader.engine import BaseEngine, MainEngine -from vnpy.trader.event import EVENT_TRADE, EVENT_ORDER, EVENT_TICK +from vnpy.trader.event import EVENT_TRADE, EVENT_ORDER, EVENT_TICK, EVENT_CONTRACT from vnpy.trader.constant import Direction, Offset, OrderType from vnpy.trader.object import ( OrderRequest, CancelRequest, SubscribeRequest, - OrderData, TradeData, TickData + OrderData, TradeData, TickData, ContractData ) from vnpy.trader.utility import load_json, save_json @@ -17,8 +17,8 @@ from vnpy.trader.utility import load_json, save_json APP_NAME = "PortfolioManager" EVENT_PORTFOLIO_UPDATE = "ePortfioUpdate" -EVENT_PORTFOLIO_ORDER = "ePortfioUpdate" -EVENT_PORTFOLIO_TRADE = "ePortfioUpdate" +EVENT_PORTFOLIO_ORDER = "ePortfioOrder" +EVENT_PORTFOLIO_TRADE = "ePortfioTrade" class PortfolioEngine(BaseEngine): @@ -35,7 +35,6 @@ class PortfolioEngine(BaseEngine): self.active_orders: Set[str] = set() self.register_event() - self.load_setting() def load_setting(self): """""" @@ -57,7 +56,7 @@ class PortfolioEngine(BaseEngine): """""" setting: dict = {} - for strategy in self.strategies: + for strategy in self.strategies.values(): setting[strategy.name] = { "name": strategy.name, "vt_symbol": strategy.vt_symbol, @@ -76,6 +75,14 @@ class PortfolioEngine(BaseEngine): self.event_engine.register(EVENT_ORDER, self.process_order_event) self.event_engine.register(EVENT_TRADE, self.process_trade_event) self.event_engine.register(EVENT_TICK, self.process_tick_event) + self.event_engine.register(EVENT_CONTRACT, self.process_contract_event) + + def process_contract_event(self, event: Event): + """""" + contract: ContractData = event.data + + if contract.vt_symbol in self.symbol_strategy_map: + self.subscribe_data(contract.vt_symbol) def process_order_event(self, event: Event): """""" @@ -102,9 +109,9 @@ class PortfolioEngine(BaseEngine): trade.vt_orderid) if strategy: strategy.update_trade( - trade.trade_direction, - trade.trade_volume, - trade.trade_price + trade.direction, + trade.volume, + trade.price ) self.put_strategy_event(strategy.name) @@ -128,7 +135,7 @@ class PortfolioEngine(BaseEngine): self, name: str, vt_symbol: str, - size: int, + size: int = 0, net_pos: int = 0, open_price: float = 0, last_price: float = 0, @@ -139,6 +146,12 @@ class PortfolioEngine(BaseEngine): if name in self.strategies: return False + if not size: + contract = self.main_engine.get_contract(vt_symbol) + if not contract: + return False + size = contract.size + strategy = PortfolioStrategy( name, vt_symbol, @@ -152,8 +165,12 @@ class PortfolioEngine(BaseEngine): self.strategies[strategy.name] = strategy self.symbol_strategy_map[strategy.vt_symbol].append(strategy) + self.save_setting() self.subscribe_data(vt_symbol) + self.put_strategy_event(name) + + return True def remove_strategy(self, name: str): """""" @@ -238,6 +255,10 @@ class PortfolioEngine(BaseEngine): event = Event(EVENT_PORTFOLIO_UPDATE, strategy) self.event_engine.put(event) + def stop(self): + """""" + self.save_setting() + class PortfolioStrategy: """""" diff --git a/vnpy/app/portfolio_manager/ui/widget.py b/vnpy/app/portfolio_manager/ui/widget.py index 43c6bf33..3abbea32 100644 --- a/vnpy/app/portfolio_manager/ui/widget.py +++ b/vnpy/app/portfolio_manager/ui/widget.py @@ -1,12 +1,10 @@ -from vnpy.event import EventEngine, Event +from vnpy.event import EventEngine from vnpy.trader.engine import MainEngine from vnpy.trader.constant import Direction, Offset -from vnpy.trader.ui import QtWidgets, QtCore, QtGui +from vnpy.trader.ui import QtWidgets, QtGui from vnpy.trader.ui.widget import ( BaseMonitor, BaseCell, - BidCell, AskCell, - TimeCell, PnlCell, - DirectionCell, EnumCell, + PnlCell, DirectionCell, EnumCell, ) from ..engine import ( @@ -43,10 +41,18 @@ class PortfolioManager(QtWidgets.QWidget): trade_monitor = PortfolioTradeMonitor( self.main_engine, self.event_engine) + trading_widget = StrategyTradingWidget(self.portfolio_engine) + management_widget = StrategyManagementWidget( + self.portfolio_engine, + trading_widget, + ) + vbox = QtWidgets.QVBoxLayout() + vbox.addWidget(management_widget) vbox.addWidget(self.create_group("策略", strategy_monitor)) vbox.addWidget(self.create_group("委托", order_monitor)) vbox.addWidget(self.create_group("成交", trade_monitor)) + vbox.addWidget(trading_widget) self.setLayout(vbox) @@ -151,3 +157,220 @@ class PortfolioOrderMonitor(BaseMonitor): order = cell.get_data() req = order.create_cancel_request() self.main_engine.cancel_order(req, order.gateway_name) + + +class StrategyTradingWidget(QtWidgets.QWidget): + """""" + + def __init__(self, portfolio_engine: PortfolioEngine): + """""" + super().__init__() + + self.portfolio_engine = portfolio_engine + self.init_ui() + self.update_combo() + + def init_ui(self): + """""" + self.name_combo = QtWidgets.QComboBox() + + self.direction_combo = QtWidgets.QComboBox() + self.direction_combo.addItems( + [Direction.LONG.value, Direction.SHORT.value]) + + self.offset_combo = QtWidgets.QComboBox() + self.offset_combo.addItems([offset.value for offset in Offset]) + + 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) + + for w in [ + self.name_combo, + self.price_line, + self.volume_line, + self.direction_combo, + self.offset_combo + ]: + w.setFixedWidth(150) + + send_button = QtWidgets.QPushButton("委托") + send_button.clicked.connect(self.send_order) + send_button.setFixedWidth(70) + + cancel_button = QtWidgets.QPushButton("全撤") + cancel_button.clicked.connect(self.cancel_all) + cancel_button.setFixedWidth(70) + + hbox = QtWidgets.QHBoxLayout() + hbox.addWidget(QtWidgets.QLabel("策略名称")) + hbox.addWidget(self.name_combo) + hbox.addWidget(QtWidgets.QLabel("方向")) + hbox.addWidget(self.direction_combo) + hbox.addWidget(QtWidgets.QLabel("开平")) + hbox.addWidget(self.offset_combo) + hbox.addWidget(QtWidgets.QLabel("价格")) + hbox.addWidget(self.price_line) + hbox.addWidget(QtWidgets.QLabel("数量")) + hbox.addWidget(self.volume_line) + hbox.addWidget(send_button) + hbox.addWidget(cancel_button) + hbox.addStretch() + + self.setLayout(hbox) + + def send_order(self): + """""" + name = self.name_combo.currentText() + + price_text = self.price_line.text() + volume_text = self.volume_line.text() + + if not price_text or not volume_text: + return + + price = float(price_text) + volume = float(volume_text) + direction = Direction(self.direction_combo.currentText()) + offset = Offset(self.offset_combo.currentText()) + + self.portfolio_engine.send_order( + name, + price, + volume, + direction, + offset + ) + + def cancel_all(self): + """""" + name = self.name_line.text() + self.portfolio_engine.cancel_all(name) + + def update_combo(self): + """""" + strategy_names = list(self.portfolio_engine.strategies.keys()) + + self.name_combo.clear() + self.name_combo.addItems(strategy_names) + + +class StrategyManagementWidget(QtWidgets.QWidget): + """""" + + def __init__( + self, + portfolio_engine: PortfolioEngine, + trading_widget: StrategyTradingWidget + ): + """""" + super().__init__() + + self.portfolio_engine = portfolio_engine + self.trading_widget = trading_widget + + self.init_ui() + self.update_combo() + + def init_ui(self): + """""" + self.name_line = QtWidgets.QLineEdit() + self.symbol_line = QtWidgets.QLineEdit() + self.remove_combo = QtWidgets.QComboBox() + + for w in [ + self.name_line, + self.symbol_line, + self.remove_combo + ]: + w.setFixedWidth(150) + + add_button = QtWidgets.QPushButton("创建策略") + add_button.clicked.connect(self.add_strategy) + + remove_button = QtWidgets.QPushButton("移除策略") + remove_button.clicked.connect(self.remove_strategy) + + hbox = QtWidgets.QHBoxLayout() + hbox.addWidget(QtWidgets.QLabel("策略名称")) + hbox.addWidget(self.name_line) + hbox.addWidget(QtWidgets.QLabel("交易合约")) + hbox.addWidget(self.symbol_line) + hbox.addWidget(add_button) + hbox.addStretch() + hbox.addWidget(self.remove_combo) + hbox.addWidget(remove_button) + + self.setLayout(hbox) + + def add_strategy(self): + """""" + name = self.name_line.text() + vt_symbol = self.symbol_line.text() + + if not name or not vt_symbol: + QtWidgets.QMessageBox.information( + self, + "提示", + "请输入策略名称和交易合约", + QtWidgets.QMessageBox.Ok + ) + + result = self.portfolio_engine.add_strategy(name, vt_symbol) + + if result: + QtWidgets.QMessageBox.information( + self, + "提示", + "策略创建成功", + QtWidgets.QMessageBox.Ok + ) + + self.update_combo() + else: + QtWidgets.QMessageBox.warning( + self, + "提示", + "策略创建失败,存在重名或找不到合约", + QtWidgets.QMessageBox.Ok + ) + + def remove_strategy(self): + """""" + name = self.remove_combo.currentText() + + if not name: + return + + result = self.portfolio_engine.remove_strategy(name) + + if result: + QtWidgets.QMessageBox.information( + self, + "提示", + "策略移除成功", + QtWidgets.QMessageBox.Ok + ) + + self.update_combo() + else: + QtWidgets.QMessageBox.warning( + self, + "提示", + "策略移除失败,不存在该策略", + QtWidgets.QMessageBox.Ok + ) + + def update_combo(self): + """""" + strategy_names = list(self.portfolio_engine.strategies.keys()) + + self.remove_combo.clear() + self.remove_combo.addItems(strategy_names) + + self.trading_widget.update_combo()