def __init__(self, start: datetime, end: datetime, parent=None): """""" super().__init__(parent) self.setWindowTitle("选择数据区间") self.start_edit = QtWidgets.QDateEdit( QtCore.QDate( start.year, start.month, start.day ) ) self.end_edit = QtWidgets.QDateEdit( QtCore.QDate( end.year, end.month, end.day ) ) button = QtWidgets.QPushButton("确定") button.clicked.connect(self.accept) form = QtWidgets.QFormLayout() form.addRow("开始时间", self.start_edit) form.addRow("结束时间", self.end_edit) form.addRow(button) self.setLayout(form)
class SpreadLogMonitor(QtWidgets.QTextEdit): """ Monitor for log data. """ signal = QtCore.pyqtSignal(Event) def __init__(self, main_engine: MainEngine, event_engine: EventEngine): """""" super().__init__() self.main_engine = main_engine self.event_engine = event_engine self.init_ui() self.register_event() def init_ui(self): """""" self.setReadOnly(True) def register_event(self): """""" self.signal.connect(self.process_log_event) self.event_engine.register(EVENT_SPREAD_LOG, self.signal.emit) def process_log_event(self, event: Event): """""" log = event.data msg = f"{log.time.strftime('%H:%M:%S')}\t{log.msg}" self.append(msg)
def _update_label(self) -> None: """""" bottom_plot = list(self._plots.values())[-1] axis_width = bottom_plot.getAxis("right").width() axis_height = bottom_plot.getAxis("bottom").height() axis_offset = QtCore.QPointF(axis_width, axis_height) bottom_view = list(self._views.values())[-1] bottom_right = bottom_view.mapSceneToView( bottom_view.sceneBoundingRect().bottomRight() - axis_offset) for plot_name, label in self._y_labels.items(): if plot_name == self._plot_name: label.setText(str(self._y)) label.show() label.setPos(bottom_right.x(), self._y) else: label.hide() dt = self._manager.get_datetime(self._x) if dt: self._x_label.setText(dt.strftime("%Y-%m-%d %H:%M:%S")) self._x_label.show() self._x_label.setPos(self._x, bottom_right.y()) self._x_label.setAnchor((0, 0))
class SpreadStrategyMonitor(QtWidgets.QWidget): """""" signal_strategy = QtCore.pyqtSignal(Event) def __init__(self, spread_engine: SpreadEngine): super().__init__() self.strategy_engine = spread_engine.strategy_engine self.main_engine = spread_engine.main_engine self.event_engine = spread_engine.event_engine self.managers = {} self.init_ui() self.register_event() def init_ui(self): """""" self.scroll_layout = QtWidgets.QVBoxLayout() self.scroll_layout.addStretch() scroll_widget = QtWidgets.QWidget() scroll_widget.setLayout(self.scroll_layout) scroll_area = QtWidgets.QScrollArea() scroll_area.setWidgetResizable(True) scroll_area.setWidget(scroll_widget) vbox = QtWidgets.QVBoxLayout() vbox.addWidget(scroll_area) self.setLayout(vbox) def register_event(self): """""" self.signal_strategy.connect(self.process_strategy_event) self.event_engine.register(EVENT_SPREAD_STRATEGY, self.signal_strategy.emit) def process_strategy_event(self, event): """ Update strategy status onto its monitor. """ data = event.data strategy_name = data["strategy_name"] if strategy_name in self.managers: manager = self.managers[strategy_name] manager.update_data(data) else: manager = SpreadStrategyWidget(self, self.strategy_engine, data) self.scroll_layout.insertWidget(0, manager) self.managers[strategy_name] = manager def remove_strategy(self, strategy_name): """""" manager = self.managers.pop(strategy_name) manager.deleteLater()
class LogMonitor(QtWidgets.QTableWidget): """""" signal = QtCore.pyqtSignal(Event) def __init__(self, event_engine: EventEngine): """""" super().__init__() self.event_engine = event_engine self.init_ui() self.register_event() def init_ui(self): """""" labels = [ "时间", "信息" ] self.setColumnCount(len(labels)) self.setHorizontalHeaderLabels(labels) self.verticalHeader().setVisible(False) self.setEditTriggers(self.NoEditTriggers) self.verticalHeader().setSectionResizeMode( QtWidgets.QHeaderView.ResizeToContents ) self.horizontalHeader().setSectionResizeMode( 1, QtWidgets.QHeaderView.Stretch ) self.setWordWrap(True) def register_event(self): """""" self.signal.connect(self.process_log_event) self.event_engine.register(EVENT_ALGO_LOG, self.signal.emit) def process_log_event(self, event): """""" log = event.data msg = log.msg timestamp = datetime.now().strftime("%H:%M:%S") timestamp_cell = QtWidgets.QTableWidgetItem(timestamp) msg_cell = QtWidgets.QTableWidgetItem(msg) self.insertRow(0) self.setItem(0, 0, timestamp_cell) self.setItem(0, 1, msg_cell)
def __init__(self, engine: ManagerEngine, parent=None): """""" super().__init__() self.engine = engine self.setWindowTitle("下载历史数据") self.setFixedWidth(300) self.setWindowFlags( (self.windowFlags() | QtCore.Qt.CustomizeWindowHint) & ~QtCore.Qt.WindowMaximizeButtonHint) self.symbol_edit = QtWidgets.QLineEdit() self.exchange_combo = QtWidgets.QComboBox() for i in Exchange: self.exchange_combo.addItem(str(i.name), i) self.interval_combo = QtWidgets.QComboBox() for i in Interval: self.interval_combo.addItem(str(i.name), i) end_dt = datetime.now() start_dt = end_dt - timedelta(days=3 * 365) self.start_date_edit = QtWidgets.QDateEdit( QtCore.QDate( start_dt.year, start_dt.month, start_dt.day ) ) button = QtWidgets.QPushButton("下载") button.clicked.connect(self.download) form = QtWidgets.QFormLayout() form.addRow("代码", self.symbol_edit) form.addRow("交易所", self.exchange_combo) form.addRow("周期", self.interval_combo) form.addRow("开始日期", self.start_date_edit) form.addRow(button) self.setLayout(form)
class RecorderManager(QtWidgets.QWidget): """""" signal_log = QtCore.pyqtSignal(Event) signal_update = QtCore.pyqtSignal(Event) signal_contract = QtCore.pyqtSignal(Event) signal_exception = QtCore.pyqtSignal(Event) def __init__(self, main_engine: MainEngine, event_engine: EventEngine): super().__init__() self.main_engine = main_engine self.event_engine = event_engine self.recorder_engine = main_engine.get_engine(APP_NAME) self.init_ui() self.register_event() self.recorder_engine.put_event() def init_ui(self): """""" self.setWindowTitle("行情记录") self.resize(1000, 600) # Create widgets self.symbol_line = QtWidgets.QLineEdit() self.symbol_line.setFixedHeight(self.symbol_line.sizeHint().height() * 2) contracts = self.main_engine.get_all_contracts() self.vt_symbols = [contract.vt_symbol for contract in contracts] self.symbol_completer = QtWidgets.QCompleter(self.vt_symbols) self.symbol_completer.setFilterMode(QtCore.Qt.MatchContains) self.symbol_completer.setCompletionMode( self.symbol_completer.PopupCompletion) self.symbol_line.setCompleter(self.symbol_completer) add_bar_button = QtWidgets.QPushButton("添加") add_bar_button.clicked.connect(self.add_bar_recording) remove_bar_button = QtWidgets.QPushButton("移除") remove_bar_button.clicked.connect(self.remove_bar_recording) add_tick_button = QtWidgets.QPushButton("添加") add_tick_button.clicked.connect(self.add_tick_recording) remove_tick_button = QtWidgets.QPushButton("移除") remove_tick_button.clicked.connect(self.remove_tick_recording) self.bar_recording_edit = QtWidgets.QTextEdit() self.bar_recording_edit.setReadOnly(True) self.tick_recording_edit = QtWidgets.QTextEdit() self.tick_recording_edit.setReadOnly(True) self.log_edit = QtWidgets.QTextEdit() self.log_edit.setReadOnly(True) # Set layout grid = QtWidgets.QGridLayout() grid.addWidget(QtWidgets.QLabel("K线记录"), 0, 0) grid.addWidget(add_bar_button, 0, 1) grid.addWidget(remove_bar_button, 0, 2) grid.addWidget(QtWidgets.QLabel("Tick记录"), 1, 0) grid.addWidget(add_tick_button, 1, 1) grid.addWidget(remove_tick_button, 1, 2) hbox = QtWidgets.QHBoxLayout() hbox.addWidget(QtWidgets.QLabel("本地代码")) hbox.addWidget(self.symbol_line) hbox.addWidget(QtWidgets.QLabel(" ")) hbox.addLayout(grid) hbox.addStretch() grid2 = QtWidgets.QGridLayout() grid2.addWidget(QtWidgets.QLabel("K线记录列表"), 0, 0) grid2.addWidget(QtWidgets.QLabel("Tick记录列表"), 0, 1) grid2.addWidget(self.bar_recording_edit, 1, 0) grid2.addWidget(self.tick_recording_edit, 1, 1) grid2.addWidget(self.log_edit, 2, 0, 1, 2) vbox = QtWidgets.QVBoxLayout() vbox.addLayout(hbox) vbox.addLayout(grid2) self.setLayout(vbox) def register_event(self): """""" self.signal_log.connect(self.process_log_event) self.signal_contract.connect(self.process_contract_event) self.signal_update.connect(self.process_update_event) self.signal_exception.connect(self.process_exception_event) self.event_engine.register(EVENT_CONTRACT, self.signal_contract.emit) self.event_engine.register(EVENT_RECORDER_LOG, self.signal_log.emit) self.event_engine.register(EVENT_RECORDER_UPDATE, self.signal_update.emit) self.event_engine.register(EVENT_RECORDER_EXCEPTION, self.signal_exception.emit) def process_log_event(self, event: Event): """""" timestamp = datetime.now().strftime("%H:%M:%S") msg = f"{timestamp}\t{event.data}" self.log_edit.append(msg) def process_update_event(self, event: Event): """""" data = event.data self.bar_recording_edit.clear() bar_text = "\n".join(data["bar"]) self.bar_recording_edit.setText(bar_text) self.tick_recording_edit.clear() tick_text = "\n".join(data["tick"]) self.tick_recording_edit.setText(tick_text) def process_contract_event(self, event: Event): """""" contract = event.data self.vt_symbols.append(contract.vt_symbol) model = self.symbol_completer.model() model.setStringList(self.vt_symbols) def process_exception_event(self, event: Event): """""" exc_info = event.data raise exc_info[1].with_traceback(exc_info[2]) def add_bar_recording(self): """""" vt_symbol = self.symbol_line.text() self.recorder_engine.add_bar_recording(vt_symbol) def add_tick_recording(self): """""" vt_symbol = self.symbol_line.text() self.recorder_engine.add_tick_recording(vt_symbol) def remove_bar_recording(self): """""" vt_symbol = self.symbol_line.text() self.recorder_engine.remove_bar_recording(vt_symbol) def remove_tick_recording(self): """""" vt_symbol = self.symbol_line.text() self.recorder_engine.remove_tick_recording(vt_symbol)
def init_ui(self): """""" self.setWindowTitle("CTA回测") # Setting Part self.class_combo = QtWidgets.QComboBox() self.symbol_line = QtWidgets.QLineEdit("IF88.CFFEX") self.interval_combo = QtWidgets.QComboBox() for interval in Interval: self.interval_combo.addItem(interval.value) end_dt = datetime.now() start_dt = end_dt - timedelta(days=3 * 365) self.start_date_edit = QtWidgets.QDateEdit( QtCore.QDate(start_dt.year, start_dt.month, start_dt.day)) self.end_date_edit = QtWidgets.QDateEdit(QtCore.QDate.currentDate()) self.rate_line = QtWidgets.QLineEdit("0.000025") self.slippage_line = QtWidgets.QLineEdit("0.2") self.size_line = QtWidgets.QLineEdit("300") self.pricetick_line = QtWidgets.QLineEdit("0.2") self.capital_line = QtWidgets.QLineEdit("1000000") self.inverse_combo = QtWidgets.QComboBox() self.inverse_combo.addItems(["正向", "反向"]) backtesting_button = QtWidgets.QPushButton("开始回测") backtesting_button.clicked.connect(self.start_backtesting) optimization_button = QtWidgets.QPushButton("参数优化") optimization_button.clicked.connect(self.start_optimization) self.result_button = QtWidgets.QPushButton("优化结果") self.result_button.clicked.connect(self.show_optimization_result) self.result_button.setEnabled(False) 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) self.candle_button = QtWidgets.QPushButton("K线图表") self.candle_button.clicked.connect(self.show_candle_chart) self.candle_button.setEnabled(False) edit_button = QtWidgets.QPushButton("代码编辑") edit_button.clicked.connect(self.edit_strategy_code) reload_button = QtWidgets.QPushButton("策略重载") reload_button.clicked.connect(self.reload_strategy_class) for button in [ backtesting_button, optimization_button, downloading_button, self.result_button, self.order_button, self.trade_button, self.daily_button, self.candle_button, edit_button, reload_button ]: button.setFixedHeight(button.sizeHint().height() * 2) form = QtWidgets.QFormLayout() form.addRow("交易策略", self.class_combo) form.addRow("本地代码", self.symbol_line) form.addRow("K线周期", self.interval_combo) form.addRow("开始日期", self.start_date_edit) form.addRow("结束日期", self.end_date_edit) form.addRow("手续费率", self.rate_line) form.addRow("交易滑点", self.slippage_line) form.addRow("合约乘数", self.size_line) form.addRow("价格跳动", self.pricetick_line) form.addRow("回测资金", self.capital_line) form.addRow("合约模式", self.inverse_combo) result_grid = QtWidgets.QGridLayout() result_grid.addWidget(self.trade_button, 0, 0) result_grid.addWidget(self.order_button, 0, 1) result_grid.addWidget(self.daily_button, 1, 0) result_grid.addWidget(self.candle_button, 1, 1) left_vbox = QtWidgets.QVBoxLayout() left_vbox.addLayout(form) left_vbox.addWidget(backtesting_button) left_vbox.addWidget(downloading_button) left_vbox.addStretch() left_vbox.addLayout(result_grid) left_vbox.addStretch() left_vbox.addWidget(optimization_button) left_vbox.addWidget(self.result_button) left_vbox.addStretch() left_vbox.addWidget(edit_button) left_vbox.addWidget(reload_button) # Result part self.statistics_monitor = StatisticsMonitor() self.log_monitor = QtWidgets.QTextEdit() self.log_monitor.setMaximumHeight(400) 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) # Candle Chart self.candle_dialog = CandleChartDialog() # Layout vbox = QtWidgets.QVBoxLayout() vbox.addWidget(self.statistics_monitor) vbox.addWidget(self.log_monitor) hbox = QtWidgets.QHBoxLayout() hbox.addLayout(left_vbox) hbox.addLayout(vbox) hbox.addWidget(self.chart) self.setLayout(hbox)
class BacktesterManager(QtWidgets.QWidget): """""" setting_filename = "cta_backtester_setting.json" signal_log = QtCore.pyqtSignal(Event) signal_backtesting_finished = QtCore.pyqtSignal(Event) signal_optimization_finished = QtCore.pyqtSignal(Event) def __init__(self, main_engine: MainEngine, event_engine: EventEngine): """""" super().__init__() self.main_engine = main_engine self.event_engine = event_engine self.backtester_engine = main_engine.get_engine(APP_NAME) self.class_names = [] self.settings = {} self.target_display = "" self.init_ui() self.register_event() self.backtester_engine.init_engine() self.init_strategy_settings() self.load_backtesting_setting() def init_strategy_settings(self): """""" self.class_names = self.backtester_engine.get_strategy_class_names() for class_name in self.class_names: setting = self.backtester_engine.get_default_setting(class_name) self.settings[class_name] = setting self.class_combo.addItems(self.class_names) def init_ui(self): """""" self.setWindowTitle("CTA回测") # Setting Part self.class_combo = QtWidgets.QComboBox() self.symbol_line = QtWidgets.QLineEdit("IF88.CFFEX") self.interval_combo = QtWidgets.QComboBox() for interval in Interval: self.interval_combo.addItem(interval.value) end_dt = datetime.now() start_dt = end_dt - timedelta(days=3 * 365) self.start_date_edit = QtWidgets.QDateEdit( QtCore.QDate(start_dt.year, start_dt.month, start_dt.day)) self.end_date_edit = QtWidgets.QDateEdit(QtCore.QDate.currentDate()) self.rate_line = QtWidgets.QLineEdit("0.000025") self.slippage_line = QtWidgets.QLineEdit("0.2") self.size_line = QtWidgets.QLineEdit("300") self.pricetick_line = QtWidgets.QLineEdit("0.2") self.capital_line = QtWidgets.QLineEdit("1000000") self.inverse_combo = QtWidgets.QComboBox() self.inverse_combo.addItems(["正向", "反向"]) backtesting_button = QtWidgets.QPushButton("开始回测") backtesting_button.clicked.connect(self.start_backtesting) optimization_button = QtWidgets.QPushButton("参数优化") optimization_button.clicked.connect(self.start_optimization) self.result_button = QtWidgets.QPushButton("优化结果") self.result_button.clicked.connect(self.show_optimization_result) self.result_button.setEnabled(False) 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) self.candle_button = QtWidgets.QPushButton("K线图表") self.candle_button.clicked.connect(self.show_candle_chart) self.candle_button.setEnabled(False) edit_button = QtWidgets.QPushButton("代码编辑") edit_button.clicked.connect(self.edit_strategy_code) reload_button = QtWidgets.QPushButton("策略重载") reload_button.clicked.connect(self.reload_strategy_class) for button in [ backtesting_button, optimization_button, downloading_button, self.result_button, self.order_button, self.trade_button, self.daily_button, self.candle_button, edit_button, reload_button ]: button.setFixedHeight(button.sizeHint().height() * 2) form = QtWidgets.QFormLayout() form.addRow("交易策略", self.class_combo) form.addRow("本地代码", self.symbol_line) form.addRow("K线周期", self.interval_combo) form.addRow("开始日期", self.start_date_edit) form.addRow("结束日期", self.end_date_edit) form.addRow("手续费率", self.rate_line) form.addRow("交易滑点", self.slippage_line) form.addRow("合约乘数", self.size_line) form.addRow("价格跳动", self.pricetick_line) form.addRow("回测资金", self.capital_line) form.addRow("合约模式", self.inverse_combo) result_grid = QtWidgets.QGridLayout() result_grid.addWidget(self.trade_button, 0, 0) result_grid.addWidget(self.order_button, 0, 1) result_grid.addWidget(self.daily_button, 1, 0) result_grid.addWidget(self.candle_button, 1, 1) left_vbox = QtWidgets.QVBoxLayout() left_vbox.addLayout(form) left_vbox.addWidget(backtesting_button) left_vbox.addWidget(downloading_button) left_vbox.addStretch() left_vbox.addLayout(result_grid) left_vbox.addStretch() left_vbox.addWidget(optimization_button) left_vbox.addWidget(self.result_button) left_vbox.addStretch() left_vbox.addWidget(edit_button) left_vbox.addWidget(reload_button) # Result part self.statistics_monitor = StatisticsMonitor() self.log_monitor = QtWidgets.QTextEdit() self.log_monitor.setMaximumHeight(400) 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) # Candle Chart self.candle_dialog = CandleChartDialog() # Layout vbox = QtWidgets.QVBoxLayout() vbox.addWidget(self.statistics_monitor) vbox.addWidget(self.log_monitor) hbox = QtWidgets.QHBoxLayout() hbox.addLayout(left_vbox) hbox.addLayout(vbox) hbox.addWidget(self.chart) self.setLayout(hbox) def load_backtesting_setting(self): """""" setting = load_json(self.setting_filename) if not setting: return self.class_combo.setCurrentIndex( self.class_combo.findText(setting["class_name"])) self.symbol_line.setText(setting["vt_symbol"]) self.interval_combo.setCurrentIndex( self.interval_combo.findText(setting["interval"])) start_str = setting.get("start", "") if start_str: start_dt = QtCore.QDate.fromString(start_str, "yyyy-MM-dd") self.start_date_edit.setDate(start_dt) self.rate_line.setText(str(setting["rate"])) self.slippage_line.setText(str(setting["slippage"])) self.size_line.setText(str(setting["size"])) self.pricetick_line.setText(str(setting["pricetick"])) self.capital_line.setText(str(setting["capital"])) if not setting["inverse"]: self.inverse_combo.setCurrentIndex(0) else: self.inverse_combo.setCurrentIndex(1) def register_event(self): """""" self.signal_log.connect(self.process_log_event) self.signal_backtesting_finished.connect( self.process_backtesting_finished_event) self.signal_optimization_finished.connect( self.process_optimization_finished_event) self.event_engine.register(EVENT_BACKTESTER_LOG, self.signal_log.emit) self.event_engine.register(EVENT_BACKTESTER_BACKTESTING_FINISHED, self.signal_backtesting_finished.emit) self.event_engine.register(EVENT_BACKTESTER_OPTIMIZATION_FINISHED, self.signal_optimization_finished.emit) def process_log_event(self, event: Event): """""" msg = event.data self.write_log(msg) def write_log(self, msg): """""" timestamp = datetime.now().strftime("%H:%M:%S") msg = f"{timestamp}\t{msg}" self.log_monitor.append(msg) def process_backtesting_finished_event(self, event: Event): """""" statistics = self.backtester_engine.get_result_statistics() self.statistics_monitor.set_data(statistics) 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) # Tick data can not be displayed using candle chart interval = self.interval_combo.currentText() if interval != Interval.TICK.value: self.candle_button.setEnabled(True) def process_optimization_finished_event(self, event: Event): """""" self.write_log("请点击[优化结果]按钮查看") self.result_button.setEnabled(True) def start_backtesting(self): """""" class_name = self.class_combo.currentText() vt_symbol = self.symbol_line.text() interval = self.interval_combo.currentText() start = self.start_date_edit.dateTime().toPyDateTime() end = self.end_date_edit.dateTime().toPyDateTime() rate = float(self.rate_line.text()) slippage = float(self.slippage_line.text()) size = float(self.size_line.text()) pricetick = float(self.pricetick_line.text()) capital = float(self.capital_line.text()) if self.inverse_combo.currentText() == "正向": inverse = False else: inverse = True # Check validity of vt_symbol if "." not in vt_symbol: self.write_log("本地代码缺失交易所后缀,请检查") return _, exchange_str = vt_symbol.split(".") if exchange_str not in Exchange.__members__: self.write_log("本地代码的交易所后缀不正确,请检查") return # Save backtesting parameters backtesting_setting = { "class_name": class_name, "vt_symbol": vt_symbol, "interval": interval, "start": start.isoformat(), "rate": rate, "slippage": slippage, "size": size, "pricetick": pricetick, "capital": capital, "inverse": inverse, } save_json(self.setting_filename, backtesting_setting) # Get strategy setting old_setting = self.settings[class_name] dialog = BacktestingSettingEditor(class_name, old_setting) i = dialog.exec() if i != dialog.Accepted: return new_setting = dialog.get_setting() self.settings[class_name] = new_setting result = self.backtester_engine.start_backtesting( class_name, vt_symbol, interval, start, end, rate, slippage, size, pricetick, capital, inverse, new_setting) 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.candle_button.setEnabled(False) self.trade_dialog.clear_data() self.order_dialog.clear_data() self.daily_dialog.clear_data() self.candle_dialog.clear_data() def start_optimization(self): """""" class_name = self.class_combo.currentText() vt_symbol = self.symbol_line.text() interval = self.interval_combo.currentText() start = self.start_date_edit.dateTime().toPyDateTime() end = self.end_date_edit.dateTime().toPyDateTime() rate = float(self.rate_line.text()) slippage = float(self.slippage_line.text()) size = float(self.size_line.text()) pricetick = float(self.pricetick_line.text()) capital = float(self.capital_line.text()) if self.inverse_combo.currentText() == "正向": inverse = False else: inverse = True parameters = self.settings[class_name] dialog = OptimizationSettingEditor(class_name, parameters) i = dialog.exec() if i != dialog.Accepted: return optimization_setting, use_ga = dialog.get_setting() self.target_display = dialog.target_display self.backtester_engine.start_optimization(class_name, vt_symbol, interval, start, end, rate, slippage, size, pricetick, capital, inverse, optimization_setting, use_ga) self.result_button.setEnabled(False) def start_downloading(self): """""" vt_symbol = self.symbol_line.text() interval = self.interval_combo.currentText() start_date = self.start_date_edit.date() end_date = self.end_date_edit.date() start = datetime(start_date.year(), start_date.month(), start_date.day(), tzinfo=get_localzone()) end = datetime(end_date.year(), end_date.month(), end_date.day(), 23, 59, 59, tzinfo=get_localzone()) self.backtester_engine.start_downloading(vt_symbol, interval, start, end) def show_optimization_result(self): """""" result_values = self.backtester_engine.get_result_values() dialog = OptimizationResultMonitor(result_values, self.target_display) 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_candle_chart(self): """""" if not self.candle_dialog.is_updated(): history = self.backtester_engine.get_history_data() self.candle_dialog.update_history(history) trades = self.backtester_engine.get_all_trades() self.candle_dialog.update_trades(trades) self.candle_dialog.exec_() def edit_strategy_code(self): """""" class_name = self.class_combo.currentText() file_path = self.backtester_engine.get_strategy_class_file(class_name) self.editor.open_editor(file_path) self.editor.show() def reload_strategy_class(self): """""" self.backtester_engine.reload_strategy_class() current_strategy_name = self.class_combo.currentText() self.class_combo.clear() self.init_strategy_settings() ix = self.class_combo.findText(current_strategy_name) self.class_combo.setCurrentIndex(ix) def show(self): """""" self.showMaximized()
class SettingMonitor(QtWidgets.QTableWidget): """""" setting_signal = QtCore.pyqtSignal(Event) use_signal = QtCore.pyqtSignal(dict) def __init__(self, algo_engine: AlgoEngine, event_engine: EventEngine): """""" super().__init__() self.algo_engine = algo_engine self.event_engine = event_engine self.settings = {} self.setting_cells = {} self.init_ui() self.register_event() def init_ui(self): """""" labels = [ "", "", "名称", "配置" ] self.setColumnCount(len(labels)) self.setHorizontalHeaderLabels(labels) self.verticalHeader().setVisible(False) self.setEditTriggers(self.NoEditTriggers) self.verticalHeader().setSectionResizeMode( QtWidgets.QHeaderView.ResizeToContents ) self.horizontalHeader().setSectionResizeMode( 3, QtWidgets.QHeaderView.Stretch ) self.setWordWrap(True) def register_event(self): """""" self.setting_signal.connect(self.process_setting_event) self.event_engine.register( EVENT_ALGO_SETTING, self.setting_signal.emit) def process_setting_event(self, event): """""" data = event.data setting_name = data["setting_name"] setting = data["setting"] cells = self.get_setting_cells(setting_name) if setting: self.settings[setting_name] = setting cells["setting"].setText(to_text(setting)) else: if setting_name in self.settings: self.settings.pop(setting_name) row = self.row(cells["setting"]) self.removeRow(row) self.setting_cells.pop(setting_name) def get_setting_cells(self, setting_name: str): """""" cells = self.setting_cells.get(setting_name, None) if not cells: use_func = partial(self.use_setting, setting_name=setting_name) use_button = QtWidgets.QPushButton("使用") use_button.clicked.connect(use_func) remove_func = partial(self.remove_setting, setting_name=setting_name) remove_button = QtWidgets.QPushButton("移除") remove_button.clicked.connect(remove_func) name_cell = QtWidgets.QTableWidgetItem(setting_name) setting_cell = QtWidgets.QTableWidgetItem() self.insertRow(0) self.setCellWidget(0, 0, use_button) self.setCellWidget(0, 1, remove_button) self.setItem(0, 2, name_cell) self.setItem(0, 3, setting_cell) cells = { "name": name_cell, "setting": setting_cell } self.setting_cells[setting_name] = cells return cells def use_setting(self, setting_name: str): """""" setting = self.settings[setting_name] setting["setting_name"] = setting_name self.use_signal.emit(setting) def remove_setting(self, setting_name: str): """""" self.algo_engine.remove_algo_setting(setting_name)
class AlgoMonitor(QtWidgets.QTableWidget): """""" parameters_signal = QtCore.pyqtSignal(Event) variables_signal = QtCore.pyqtSignal(Event) def __init__( self, algo_engine: AlgoEngine, event_engine: EventEngine, mode_active: bool ): """""" super().__init__() self.algo_engine = algo_engine self.event_engine = event_engine self.mode_active = mode_active self.algo_cells = {} self.init_ui() self.register_event() def init_ui(self): """""" labels = [ "", "算法", "参数", "状态" ] self.setColumnCount(len(labels)) self.setHorizontalHeaderLabels(labels) self.verticalHeader().setVisible(False) self.setEditTriggers(self.NoEditTriggers) self.verticalHeader().setSectionResizeMode( QtWidgets.QHeaderView.ResizeToContents ) for column in range(2, 4): self.horizontalHeader().setSectionResizeMode( column, QtWidgets.QHeaderView.Stretch ) self.setWordWrap(True) if not self.mode_active: self.hideColumn(0) def register_event(self): """""" self.parameters_signal.connect(self.process_parameters_event) self.variables_signal.connect(self.process_variables_event) self.event_engine.register( EVENT_ALGO_PARAMETERS, self.parameters_signal.emit) self.event_engine.register( EVENT_ALGO_VARIABLES, self.variables_signal.emit) def process_parameters_event(self, event): """""" data = event.data algo_name = data["algo_name"] parameters = data["parameters"] cells = self.get_algo_cells(algo_name) text = to_text(parameters) cells["parameters"].setText(text) def process_variables_event(self, event): """""" data = event.data algo_name = data["algo_name"] variables = data["variables"] cells = self.get_algo_cells(algo_name) variables_cell = cells["variables"] text = to_text(variables) variables_cell.setText(text) row = self.row(variables_cell) active = variables["active"] if self.mode_active: if active: self.showRow(row) else: self.hideRow(row) else: if active: self.hideRow(row) else: self.showRow(row) def stop_algo(self, algo_name: str): """""" self.algo_engine.stop_algo(algo_name) def get_algo_cells(self, algo_name: str): """""" cells = self.algo_cells.get(algo_name, None) if not cells: stop_func = partial(self.stop_algo, algo_name=algo_name) stop_button = QtWidgets.QPushButton("停止") stop_button.clicked.connect(stop_func) name_cell = QtWidgets.QTableWidgetItem(algo_name) parameters_cell = QtWidgets.QTableWidgetItem() variables_cell = QtWidgets.QTableWidgetItem() self.insertRow(0) self.setCellWidget(0, 0, stop_button) self.setItem(0, 1, name_cell) self.setItem(0, 2, parameters_cell) self.setItem(0, 3, variables_cell) cells = { "name": name_cell, "parameters": parameters_cell, "variables": variables_cell } self.algo_cells[algo_name] = cells return cells
class ChartWizardWidget(QtWidgets.QWidget): """""" signal_tick = QtCore.pyqtSignal(Event) signal_history = QtCore.pyqtSignal(Event) def __init__(self, main_engine: MainEngine, event_engine: EventEngine): """""" super().__init__() self.main_engine = main_engine self.event_engine = event_engine self.chart_engine: ChartWizardEngine = main_engine.get_engine(APP_NAME) self.bgs: Dict[str, BarGenerator] = {} self.charts: Dict[str, ChartWidget] = {} self.init_ui() self.register_event() def init_ui(self) -> None: """""" self.setWindowTitle("K线图表") self.tab: QtWidgets.QTabWidget = QtWidgets.QTabWidget() self.symbol_line: QtWidgets.QLineEdit = QtWidgets.QLineEdit() self.button = QtWidgets.QPushButton("新建图表") self.button.clicked.connect(self.new_chart) hbox = QtWidgets.QHBoxLayout() hbox.addWidget(QtWidgets.QLabel("本地代码")) hbox.addWidget(self.symbol_line) hbox.addWidget(self.button) hbox.addStretch() vbox = QtWidgets.QVBoxLayout() vbox.addLayout(hbox) vbox.addWidget(self.tab) self.setLayout(vbox) def create_chart(self) -> ChartWidget: """""" chart = ChartWidget() chart.add_plot("candle", hide_x_axis=True) chart.add_plot("volume", maximum_height=200) chart.add_item(CandleItem, "candle", "candle") chart.add_item(VolumeItem, "volume", "volume") chart.add_cursor() return chart def new_chart(self) -> None: """""" # Filter invalid vt_symbol vt_symbol = self.symbol_line.text() if not vt_symbol: return if vt_symbol in self.charts: return contract = self.main_engine.get_contract(vt_symbol) if not contract: return # Create new chart self.bgs[vt_symbol] = BarGenerator(self.on_bar) chart = self.create_chart() self.charts[vt_symbol] = chart self.tab.addTab(chart, vt_symbol) # Query history data end = datetime.now(get_localzone()) start = end - timedelta(days=5) self.chart_engine.query_history(vt_symbol, Interval.MINUTE, start, end) def register_event(self) -> None: """""" self.signal_tick.connect(self.process_tick_event) self.signal_history.connect(self.process_history_event) self.event_engine.register(EVENT_CHART_HISTORY, self.signal_history.emit) self.event_engine.register(EVENT_TICK, self.signal_tick.emit) def process_tick_event(self, event: Event) -> None: """""" tick: TickData = event.data bg = self.bgs.get(tick.vt_symbol, None) if bg: bg.update_tick(tick) chart = self.charts[tick.vt_symbol] bar = copy(bg.bar) bar.datetime = bar.datetime.replace(second=0, microsecond=0) chart.update_bar(bar) def process_history_event(self, event: Event) -> None: """""" history: List[BarData] = event.data if not history: return bar = history[0] chart = self.charts[bar.vt_symbol] chart.update_history(history) # Subscribe following data update contract = self.main_engine.get_contract(bar.vt_symbol) req = SubscribeRequest(contract.symbol, contract.exchange) self.main_engine.subscribe(req, contract.gateway_name) def on_bar(self, bar: BarData): """""" chart = self.charts[bar.vt_symbol] chart.update_bar(bar)
class PortfolioManager(QtWidgets.QWidget): """""" signal_contract = QtCore.pyqtSignal(Event) signal_portfolio = QtCore.pyqtSignal(Event) signal_trade = QtCore.pyqtSignal(Event) def __init__(self, main_engine: MainEngine, event_engine: EventEngine) -> None: """""" super().__init__() self.main_engine = main_engine self.event_engine = event_engine self.portfolio_engine: PortfolioEngine = main_engine.get_engine( APP_NAME) self.portfolio_items: Dict[str, QtWidgets.QTreeWidgetItem] = {} self.init_ui() self.register_event() self.update_trades() def init_ui(self) -> None: """""" self.setWindowTitle("投资组合") labels = [ "组合名称", "本地代码", "开盘仓位", "当前仓位", "交易盈亏", "持仓盈亏", "总盈亏", "多头成交", "空头成交" ] self.column_count = len(labels) self.tree = QtWidgets.QTreeWidget() self.tree.setColumnCount(self.column_count) self.tree.setHeaderLabels(labels) self.tree.header().setDefaultAlignment(QtCore.Qt.AlignCenter) self.tree.header().setStretchLastSection(False) delegate = TreeDelegate() self.tree.setItemDelegate(delegate) self.monitor = PortfolioTradeMonitor() expand_button = QtWidgets.QPushButton("全部展开") expand_button.clicked.connect(self.tree.expandAll) collapse_button = QtWidgets.QPushButton("全部折叠") collapse_button.clicked.connect(self.tree.collapseAll) resize_button = QtWidgets.QPushButton("调整列宽") resize_button.clicked.connect(self.resize_columns) interval_spin = QtWidgets.QSpinBox() interval_spin.setMinimum(1) interval_spin.setMaximum(60) interval_spin.setSuffix("秒") interval_spin.setValue(self.portfolio_engine.get_timer_interval()) interval_spin.valueChanged.connect( self.portfolio_engine.set_timer_interval) self.reference_combo = QtWidgets.QComboBox() self.reference_combo.setMinimumWidth(200) self.reference_combo.addItem("") self.reference_combo.currentIndexChanged.connect( self.set_reference_filter) hbox1 = QtWidgets.QHBoxLayout() hbox1.addWidget(expand_button) hbox1.addWidget(collapse_button) hbox1.addWidget(resize_button) hbox1.addStretch() hbox1.addWidget(QtWidgets.QLabel("刷新频率")) hbox1.addWidget(interval_spin) hbox1.addStretch() hbox1.addWidget(QtWidgets.QLabel("组合成交")) hbox1.addWidget(self.reference_combo) hbox2 = QtWidgets.QHBoxLayout() hbox2.addWidget(self.tree) hbox2.addWidget(self.monitor) vbox = QtWidgets.QVBoxLayout() vbox.addLayout(hbox1) vbox.addLayout(hbox2) self.setLayout(vbox) def register_event(self) -> None: """""" self.signal_contract.connect(self.process_contract_event) self.signal_portfolio.connect(self.process_portfolio_event) self.signal_trade.connect(self.process_trade_event) self.event_engine.register(EVENT_PM_CONTRACT, self.signal_contract.emit) self.event_engine.register(EVENT_PM_PORTFOLIO, self.signal_portfolio.emit) self.event_engine.register(EVENT_TRADE, self.signal_trade.emit) def update_trades(self) -> None: """""" trades = self.main_engine.get_all_trades() for trade in trades: # Ignore trade with no order reference if hasattr(trade, "reference"): self.monitor.update_trade(trade) def get_portfolio_item(self, reference: str) -> QtWidgets.QTreeWidgetItem: """""" portfolio_item = self.portfolio_items.get(reference, None) if not portfolio_item: portfolio_item = QtWidgets.QTreeWidgetItem() portfolio_item.setText(0, reference) for i in range(2, self.column_count): portfolio_item.setTextAlignment(i, QtCore.Qt.AlignCenter) self.portfolio_items[reference] = portfolio_item self.tree.addTopLevelItem(portfolio_item) self.reference_combo.addItem(reference) return portfolio_item def get_contract_item(self, reference: str, vt_symbol: str) -> QtWidgets.QTreeWidgetItem: """""" key = (reference, vt_symbol) contract_item = self.contract_items.get(key, None) if not contract_item: contract_item = QtWidgets.QTreeWidgetItem() contract_item.setText(1, vt_symbol) for i in range(2, self.column_count): contract_item.setTextAlignment(i, QtCore.Qt.AlignCenter) self.contract_items[key] = contract_item portfolio_item = self.get_portfolio_item(reference) portfolio_item.addChild(contract_item) return contract_item def process_contract_event(self, event: Event) -> None: """""" contract_result: ContractResult = event.data contract_item = self.get_contract_item(contract_result.reference, contract_result.vt_symbol) contract_item.setText(2, str(contract_result.open_pos)) contract_item.setText(3, str(contract_result.last_pos)) contract_item.setText(4, str(contract_result.trading_pnl)) contract_item.setText(5, str(contract_result.holding_pnl)) contract_item.setText(6, str(contract_result.total_pnl)) contract_item.setText(7, str(contract_result.long_volume)) contract_item.setText(8, str(contract_result.short_volume)) self.update_item_color(contract_item, contract_result) def process_portfolio_event(self, event: Event) -> None: """""" portfolio_result: PortfolioResult = event.data portfolio_item = self.get_portfolio_item(portfolio_result.reference) portfolio_item.setText(4, str(portfolio_result.trading_pnl)) portfolio_item.setText(5, str(portfolio_result.holding_pnl)) portfolio_item.setText(6, str(portfolio_result.total_pnl)) self.update_item_color(portfolio_item, portfolio_result) def process_trade_event(self, event: Event) -> None: """""" trade: TradeData = event.data self.monitor.update_trade(trade) def update_item_color(self, item: QtWidgets.QTreeWidgetItem, result: Union[ContractResult, PortfolioResult]): start_column = 4 for n, pnl in enumerate( [result.trading_pnl, result.holding_pnl, result.total_pnl]): i = n + start_column if pnl > 0: item.setForeground(i, RED_COLOR) elif pnl < 0: item.setForeground(i, GREEN_COLOR) else: item.setForeground(i, WHITE_COLOR) def resize_columns(self) -> None: """""" for i in range(self.column_count): self.tree.resizeColumnToContents(i) def set_reference_filter(self, filter: str) -> None: """""" filter = self.reference_combo.currentText() self.monitor.set_filter(filter) def show(self) -> None: """""" self.showMaximized()
class CtaManager(QtWidgets.QWidget): """""" signal_log = QtCore.pyqtSignal(Event) signal_strategy = QtCore.pyqtSignal(Event) def __init__(self, main_engine: MainEngine, event_engine: EventEngine): super(CtaManager, self).__init__() self.main_engine = main_engine self.event_engine = event_engine self.cta_engine = main_engine.get_engine(APP_NAME) self.managers = {} self.init_ui() self.register_event() self.cta_engine.init_engine() self.update_class_combo() def init_ui(self): """""" self.setWindowTitle("CTA策略") # Create widgets self.class_combo = QtWidgets.QComboBox() add_button = QtWidgets.QPushButton("添加策略") add_button.clicked.connect(self.add_strategy) init_button = QtWidgets.QPushButton("全部初始化") init_button.clicked.connect(self.cta_engine.init_all_strategies) start_button = QtWidgets.QPushButton("全部启动") start_button.clicked.connect(self.cta_engine.start_all_strategies) stop_button = QtWidgets.QPushButton("全部停止") stop_button.clicked.connect(self.cta_engine.stop_all_strategies) clear_button = QtWidgets.QPushButton("清空日志") clear_button.clicked.connect(self.clear_log) self.scroll_layout = QtWidgets.QVBoxLayout() self.scroll_layout.addStretch() scroll_widget = QtWidgets.QWidget() scroll_widget.setLayout(self.scroll_layout) scroll_area = QtWidgets.QScrollArea() scroll_area.setWidgetResizable(True) scroll_area.setWidget(scroll_widget) self.log_monitor = LogMonitor(self.main_engine, self.event_engine) self.stop_order_monitor = StopOrderMonitor(self.main_engine, self.event_engine) # Set layout hbox1 = QtWidgets.QHBoxLayout() hbox1.addWidget(self.class_combo) hbox1.addWidget(add_button) hbox1.addStretch() hbox1.addWidget(init_button) hbox1.addWidget(start_button) hbox1.addWidget(stop_button) hbox1.addWidget(clear_button) grid = QtWidgets.QGridLayout() grid.addWidget(scroll_area, 0, 0, 2, 1) grid.addWidget(self.stop_order_monitor, 0, 1) grid.addWidget(self.log_monitor, 1, 1) vbox = QtWidgets.QVBoxLayout() vbox.addLayout(hbox1) vbox.addLayout(grid) self.setLayout(vbox) def update_class_combo(self): """""" self.class_combo.addItems( self.cta_engine.get_all_strategy_class_names()) def register_event(self): """""" self.signal_strategy.connect(self.process_strategy_event) self.event_engine.register(EVENT_CTA_STRATEGY, self.signal_strategy.emit) def process_strategy_event(self, event): """ Update strategy status onto its monitor. """ data = event.data strategy_name = data["strategy_name"] if strategy_name in self.managers: manager = self.managers[strategy_name] manager.update_data(data) else: manager = StrategyManager(self, self.cta_engine, data) self.scroll_layout.insertWidget(0, manager) self.managers[strategy_name] = manager def remove_strategy(self, strategy_name): """""" manager = self.managers.pop(strategy_name) manager.deleteLater() def add_strategy(self): """""" class_name = str(self.class_combo.currentText()) if not class_name: return parameters = self.cta_engine.get_strategy_class_parameters(class_name) editor = SettingEditor(parameters, class_name=class_name) n = editor.exec_() if n == editor.Accepted: setting = editor.get_setting() vt_symbol = setting.pop("vt_symbol") strategy_name = setting.pop("strategy_name") self.cta_engine.add_strategy(class_name, strategy_name, vt_symbol, setting) def clear_log(self): """""" self.log_monitor.setRowCount(0) def show(self): """""" self.showMaximized()