diff --git a/vnpy/app/portfolio_manager/engine.py b/vnpy/app/portfolio_manager/engine.py index 0d166b4c..f78f70de 100644 --- a/vnpy/app/portfolio_manager/engine.py +++ b/vnpy/app/portfolio_manager/engine.py @@ -5,7 +5,9 @@ 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, EVENT_CONTRACT +from vnpy.trader.event import ( + EVENT_TRADE, EVENT_ORDER, EVENT_TICK, EVENT_CONTRACT, EVENT_TIMER +) from vnpy.trader.constant import Direction, Offset, OrderType from vnpy.trader.object import ( OrderRequest, CancelRequest, SubscribeRequest, @@ -29,6 +31,8 @@ class PortfolioEngine(BaseEngine): """""" super().__init__(main_engine, event_engine, APP_NAME) + self.inited = False + self.strategies: Dict[str, PortfolioStrategy] = {} self.symbol_strategy_map: Dict[str, List] = defaultdict(list) self.order_strategy_map: Dict[str, PortfolioStrategy] = {} @@ -36,6 +40,14 @@ class PortfolioEngine(BaseEngine): self.register_event() + def init_engine(self): + """""" + if self.inited: + return + self.inited = True + + self.load_setting() + def load_setting(self): """""" setting: dict = load_json(self.setting_filename) @@ -76,6 +88,12 @@ class PortfolioEngine(BaseEngine): 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) + self.event_engine.register(EVENT_TIMER, self.process_timer_event) + + def process_timer_event(self, event: Event): + """""" + if self.inited: + self.save_setting() def process_contract_event(self, event: Event): """""" @@ -121,6 +139,8 @@ class PortfolioEngine(BaseEngine): event = Event(EVENT_PORTFOLIO_TRADE, strategy_trade) self.event_engine.put(event) + self.save_setting() + def process_tick_event(self, event: Event): """""" tick: TickData = event.data @@ -245,7 +265,7 @@ class PortfolioEngine(BaseEngine): def cancel_all(self, name: str): """""" for vt_orderid in self.active_orders: - strategy = self.order_symbol_map[vt_orderid] + strategy = self.order_strategy_map[vt_orderid] if strategy.name == name: self.cancel_order(vt_orderid) @@ -308,10 +328,46 @@ class PortfolioStrategy: trade_price: float ): """""" + old_cost = self.net_pos * self.open_price + if trade_direction == Direction.LONG: - self.net_pos += trade_volume + new_pos = self.net_pos + trade_volume + + # Open new long position + if self.net_pos >= 0: + new_cost = old_cost + trade_volume * trade_price + self.open_price = new_cost / new_pos + # Close short position + else: + close_volume = min(trade_volume, abs(self.net_pos)) + realized_pnl = (trade_price - self.open_price) * \ + close_volume * (-1) + self.realized_pnl += realized_pnl + + if new_pos > 0: + self.open_price = trade_price + + # Update net pos + self.net_pos = new_pos + else: - self.net_pos -= trade_volume + new_pos = self.net_pos - trade_volume + + # Open new short position + if self.net_pos <= 0: + new_cost = old_cost - trade_volume * trade_price + self.open_price = new_cost / new_pos + # Close long position + else: + close_volume = min(trade_volume, abs(self.net_pos)) + realized_pnl = (trade_price - self.open_price) * close_volume + self.realized_pnl += realized_pnl + + if new_pos < 0: + self.open_price = trade_price + + # Update net pos + self.net_pos = new_pos self.calculate_pnl() diff --git a/vnpy/app/portfolio_manager/ui/widget.py b/vnpy/app/portfolio_manager/ui/widget.py index 3abbea32..0126247c 100644 --- a/vnpy/app/portfolio_manager/ui/widget.py +++ b/vnpy/app/portfolio_manager/ui/widget.py @@ -41,24 +41,26 @@ class PortfolioManager(QtWidgets.QWidget): trade_monitor = PortfolioTradeMonitor( self.main_engine, self.event_engine) - trading_widget = StrategyTradingWidget(self.portfolio_engine) - management_widget = StrategyManagementWidget( + self.trading_widget = StrategyTradingWidget(self.portfolio_engine) + self.management_widget = StrategyManagementWidget( self.portfolio_engine, - trading_widget, + self.trading_widget, ) vbox = QtWidgets.QVBoxLayout() - vbox.addWidget(management_widget) + vbox.addWidget(self.management_widget) vbox.addWidget(self.create_group("策略", strategy_monitor)) + vbox.addWidget(self.trading_widget) vbox.addWidget(self.create_group("委托", order_monitor)) vbox.addWidget(self.create_group("成交", trade_monitor)) - vbox.addWidget(trading_widget) self.setLayout(vbox) def show(self): """""" - self.portfolio_engine.load_setting() + self.portfolio_engine.init_engine() + self.management_widget.update_combo() + self.showMaximized() def create_group(self, title: str, widget: QtWidgets.QWidget): @@ -91,6 +93,7 @@ class PortfolioStrategyMonitor(BaseMonitor): "open_price": {"display": "持仓价格", "cell": BaseCell, "update": True}, "last_price": {"display": "最新价格", "cell": BaseCell, "update": True}, "pos_pnl": {"display": "持仓盈亏", "cell": PnlCell, "update": True}, + "realized_pnl": {"display": "平仓盈亏", "cell": PnlCell, "update": True}, "create_time": {"display": "创建时间", "cell": BaseCell, "update": False}, } @@ -104,7 +107,7 @@ class PortfolioTradeMonitor(BaseMonitor): data_key = "" headers = { - "gateway_name": {"display": "接口名称", "cell": BaseCell, "update": False}, + "gateway_name": {"display": "策略名称", "cell": BaseCell, "update": False}, "tradeid": {"display": "成交号 ", "cell": BaseCell, "update": False}, "orderid": {"display": "委托号", "cell": BaseCell, "update": False}, "symbol": {"display": "代码", "cell": BaseCell, "update": False}, @@ -127,7 +130,7 @@ class PortfolioOrderMonitor(BaseMonitor): sorting = True headers = { - "gateway_name": {"display": "接口名称", "cell": BaseCell, "update": False}, + "gateway_name": {"display": "策略名称", "cell": BaseCell, "update": False}, "orderid": {"display": "委托号", "cell": BaseCell, "update": False}, "symbol": {"display": "代码", "cell": BaseCell, "update": False}, "exchange": {"display": "交易所", "cell": EnumCell, "update": False}, @@ -249,7 +252,7 @@ class StrategyTradingWidget(QtWidgets.QWidget): def cancel_all(self): """""" - name = self.name_line.text() + name = self.name_combo.currentText() self.portfolio_engine.cancel_all(name) def update_combo(self):