diff --git a/vnpy/app/cta_backtester/ui/widget.py b/vnpy/app/cta_backtester/ui/widget.py index 04cb7349..fddf5573 100644 --- a/vnpy/app/cta_backtester/ui/widget.py +++ b/vnpy/app/cta_backtester/ui/widget.py @@ -12,6 +12,7 @@ from ..engine import ( from vnpy.trader.constant import Interval from vnpy.trader.engine import MainEngine from vnpy.trader.ui import QtCore, QtWidgets, QtGui +from vnpy.trader.ui.widget import BaseMonitor, BaseCell, DirectionCell, EnumCell from vnpy.event import Event, EventEngine @@ -95,11 +96,26 @@ class BacktesterManager(QtWidgets.QWidget): downloading_button = QtWidgets.QPushButton("下载数据") downloading_button.clicked.connect(self.start_downloading) + self.order_button = QtWidgets.QPushButton("委托记录") + self.order_button.clicked.connect(self.show_backtesting_orders) + self.order_button.setEnabled(False) + + self.trade_button = QtWidgets.QPushButton("成交记录") + self.trade_button.clicked.connect(self.show_backtesting_trades) + self.trade_button.setEnabled(False) + + self.daily_button = QtWidgets.QPushButton("每日盈亏") + self.daily_button.clicked.connect(self.show_daily_results) + self.daily_button.setEnabled(False) + for button in [ backtesting_button, optimization_button, downloading_button, - self.result_button + self.result_button, + self.order_button, + self.trade_button, + self.daily_button ]: button.setFixedHeight(button.sizeHint().height() * 2) @@ -114,12 +130,16 @@ class BacktesterManager(QtWidgets.QWidget): form.addRow("合约乘数", self.size_line) form.addRow("价格跳动", self.pricetick_line) form.addRow("回测资金", self.capital_line) - form.addRow(backtesting_button) - + left_vbox = QtWidgets.QVBoxLayout() left_vbox.addLayout(form) + left_vbox.addWidget(backtesting_button) left_vbox.addWidget(downloading_button) left_vbox.addStretch() + left_vbox.addWidget(self.trade_button) + left_vbox.addWidget(self.order_button) + left_vbox.addWidget(self.daily_button) + left_vbox.addStretch() left_vbox.addWidget(optimization_button) left_vbox.addWidget(self.result_button) @@ -132,6 +152,25 @@ class BacktesterManager(QtWidgets.QWidget): self.chart = BacktesterChart() self.chart.setMinimumWidth(1000) + self.trade_dialog = BacktestingResultDialog( + self.main_engine, + self.event_engine, + "回测成交记录", + BacktestingTradeMonitor + ) + self.order_dialog = BacktestingResultDialog( + self.main_engine, + self.event_engine, + "回测委托记录", + BacktestingOrderMonitor + ) + self.daily_dialog = BacktestingResultDialog( + self.main_engine, + self.event_engine, + "回测每日盈亏", + DailyResultMonitor + ) + # Layout vbox = QtWidgets.QVBoxLayout() vbox.addWidget(self.statistics_monitor) @@ -176,6 +215,10 @@ class BacktesterManager(QtWidgets.QWidget): df = self.backtester_engine.get_result_df() self.chart.set_data(df) + self.trade_button.setEnabled(True) + self.order_button.setEnabled(True) + self.daily_button.setEnabled(True) + def process_optimization_finished_event(self, event: Event): """""" self.write_log("请点击[优化结果]按钮查看") @@ -220,6 +263,14 @@ class BacktesterManager(QtWidgets.QWidget): if result: self.statistics_monitor.clear_data() self.chart.clear_data() + + self.trade_button.setEnabled(False) + self.order_button.setEnabled(False) + self.daily_button.setEnabled(False) + + self.trade_dialog.clear_data() + self.order_dialog.clear_data() + self.daily_dialog.clear_data() def start_optimization(self): """""" @@ -284,6 +335,30 @@ class BacktesterManager(QtWidgets.QWidget): ) dialog.exec_() + def show_backtesting_trades(self): + """""" + if not self.trade_dialog.is_updated(): + trades = self.backtester_engine.get_all_trades() + self.trade_dialog.update_data(trades) + + self.trade_dialog.exec_() + + def show_backtesting_orders(self): + """""" + if not self.order_dialog.is_updated(): + orders = self.backtester_engine.get_all_orders() + self.order_dialog.update_data(orders) + + self.order_dialog.exec_() + + def show_daily_results(self): + """""" + if not self.daily_dialog.is_updated(): + results = self.backtester_engine.get_all_daily_results() + self.daily_dialog.update_data(results) + + self.daily_dialog.exec_() + def show(self): """""" self.showMaximized() @@ -750,3 +825,117 @@ class OptimizationResultMonitor(QtWidgets.QDialog): vbox.addWidget(table) self.setLayout(vbox) + + +class BacktestingTradeMonitor(BaseMonitor): + """ + Monitor for backtesting trade data. + """ + + headers = { + "tradeid": {"display": "成交号 ", "cell": BaseCell, "update": False}, + "orderid": {"display": "委托号", "cell": BaseCell, "update": False}, + "symbol": {"display": "代码", "cell": BaseCell, "update": False}, + "exchange": {"display": "交易所", "cell": EnumCell, "update": False}, + "direction": {"display": "方向", "cell": DirectionCell, "update": False}, + "offset": {"display": "开平", "cell": EnumCell, "update": False}, + "price": {"display": "价格", "cell": BaseCell, "update": False}, + "volume": {"display": "数量", "cell": BaseCell, "update": False}, + "datetime": {"display": "时间", "cell": BaseCell, "update": False}, + "gateway_name": {"display": "接口", "cell": BaseCell, "update": False}, + } + + +class BacktestingOrderMonitor(BaseMonitor): + """ + Monitor for backtesting order data. + """ + + headers = { + "orderid": {"display": "委托号", "cell": BaseCell, "update": False}, + "symbol": {"display": "代码", "cell": BaseCell, "update": False}, + "exchange": {"display": "交易所", "cell": EnumCell, "update": False}, + "type": {"display": "类型", "cell": EnumCell, "update": False}, + "direction": {"display": "方向", "cell": DirectionCell, "update": False}, + "offset": {"display": "开平", "cell": EnumCell, "update": False}, + "price": {"display": "价格", "cell": BaseCell, "update": False}, + "volume": {"display": "总数量", "cell": BaseCell, "update": False}, + "traded": {"display": "已成交", "cell": BaseCell, "update": False}, + "status": {"display": "状态", "cell": EnumCell, "update": False}, + "datetime": {"display": "时间", "cell": BaseCell, "update": False}, + "gateway_name": {"display": "接口", "cell": BaseCell, "update": False}, + } + + +class DailyResultMonitor(BaseMonitor): + """ + Monitor for backtesting daily result. + """ + + headers = { + "date": {"display": "日期", "cell": BaseCell, "update": False}, + "trade_count": {"display": "成交笔数", "cell": BaseCell, "update": False}, + "start_pos": {"display": "开盘持仓", "cell": BaseCell, "update": False}, + "end_pos": {"display": "收盘持仓", "cell": BaseCell, "update": False}, + "turnover": {"display": "成交额", "cell": BaseCell, "update": False}, + "commission": {"display": "手续费", "cell": BaseCell, "update": False}, + "slippage": {"display": "滑点", "cell": BaseCell, "update": False}, + "trading_pnl": {"display": "交易盈亏", "cell": BaseCell, "update": False}, + "holding_pnl": {"display": "持仓盈亏", "cell": BaseCell, "update": False}, + "total_pnl": {"display": "总盈亏", "cell": BaseCell, "update": False}, + "net_pnl": {"display": "净盈亏", "cell": BaseCell, "update": False}, + } + + +class BacktestingResultDialog(QtWidgets.QDialog): + """ + """ + + def __init__( + self, + main_engine: MainEngine, + event_engine: EventEngine, + title: str, + table_class: QtWidgets.QTableWidget + ): + """""" + super().__init__() + + self.main_engine = main_engine + self.event_engine = event_engine + self.title = title + self.table_class = table_class + + self.updated = False + + self.init_ui() + + def init_ui(self): + """""" + self.setWindowTitle(self.title) + self.resize(1100, 600) + + self.table = self.table_class(self.main_engine, self.event_engine) + + vbox = QtWidgets.QVBoxLayout() + vbox.addWidget(self.table) + + self.setLayout(vbox) + + def clear_data(self): + """""" + self.updated = False + self.table.setRowCount(0) + + def update_data(self, data: list): + """""" + self.updated = True + + data.reverse() + for obj in data: + self.table.insert_new_row(obj) + + def is_updated(self): + """""" + return self.updated +