class QA_GUI_Selected_TaskQueue(QtCore.QThread): # QThread 继承的不执行__init__ # def __int__(self, logDisplay): # 奇怪的问题, 不执行 __init__ # 初始化函数,默认 # super().__init__() # sfassda #print("run here") # exit(0) #self.logDisplay = logDisplay # sys.stderr.textWritten.connect(self.outputWrittenStderr) # 下面将print 系统输出重定向到textEdit中 #sys.stdout = EmittingStream() #sys.stderr = EmittingStream() # 接收信号str的信号槽 ''' def outputWrittenStdout(self, text): cursor = self.logDisplay.textCursor() cursor.movePosition(QtGui.QTextCursor.End) cursor.insertText(text) self.logDisplay.setTextCursor(cursor) self.logDisplay.ensureCursorVisible() def outputWrittenStderr(self, text): cursor = self.logDisplay.textCursor() cursor.movePosition(QtGui.QTextCursor.End) cursor.insertText(text) self.logDisplay.setTextCursor(cursor) self.logDisplay.ensureCursorVisible() ''' # 定义一个信号, trigger_all_task_start = QtCore.pyqtSignal(str) trigger_all_task_done = QtCore.pyqtSignal(str) # 定义任务(每个是一个线程) QA_GUI_Task_List = [] def run(self): self.trigger_all_task_start.emit('all_task_start') for iSubTask in self.QA_GUI_Task_List: iSubTask.start() # wait finish iSubTask while (iSubTask.isRunning()): time.sleep(1) self.trigger_all_task_done.emit('all_task_done') def putTask(self, subTask): self.QA_GUI_Task_List.append(subTask) def clearTask(self): self.QA_GUI_Task_List.clear()
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 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)
class LogMonitorBox(QtWidgets.QVBoxLayout): signal_log = QtCore.pyqtSignal(Event) def __init__(self, editor_manager): super().__init__() self.event_engine = editor_manager.event_engine self.log_monitor = None self.register_event() self.init_ui() def init_ui(self): self.log_monitor = QtWidgets.QTextEdit() self.log_monitor.setReadOnly(True) clear_button = QtWidgets.QPushButton("清空日志") clear_button.clicked.connect(self.log_monitor.clear) self.addWidget(self.log_monitor) self.addWidget(clear_button) def register_event(self): """""" self.signal_log.connect(self.process_log_event) self.event_engine.register(EVENT_EDITOR_LOG, self.signal_log.emit) def process_log_event(self, event: Event): """""" log = event.data msg = f"{log.time}\t{log.msg}" self.log_monitor.append(msg)
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): """""" msg = event.data 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)
class OptionManager(QtWidgets.QWidget): """""" signal_new_portfolio = 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.option_engine = main_engine.get_engine(APP_NAME) self.portfolio_name: str = "" self.market_monitor: OptionMarketMonitor = None self.greeks_monitor: OptionGreeksMonitor = None self.volatility_chart: OptionVolatilityChart = None self.chain_monitor: OptionChainMonitor = None self.manual_trader: OptionManualTrader = None self.hedge_widget: OptionHedgeWidget = None self.scenario_chart: ScenarioAnalysisChart = None self.eye_manager: ElectronicEyeManager = None self.pricing_manager: PricingVolatilityManager = None self.init_ui() self.register_event() def init_ui(self) -> None: """""" self.setWindowTitle("OptionMaster") self.portfolio_combo = QtWidgets.QComboBox() self.portfolio_combo.setFixedWidth(150) self.update_portfolio_combo() self.portfolio_button = QtWidgets.QPushButton("配置") self.portfolio_button.clicked.connect(self.open_portfolio_dialog) self.market_button = QtWidgets.QPushButton("T型报价") self.greeks_button = QtWidgets.QPushButton("持仓希腊值") self.chain_button = QtWidgets.QPushButton("升贴水监控") self.manual_button = QtWidgets.QPushButton("快速交易") self.volatility_button = QtWidgets.QPushButton("波动率曲线") self.hedge_button = QtWidgets.QPushButton("Delta对冲") self.scenario_button = QtWidgets.QPushButton("情景分析") self.eye_button = QtWidgets.QPushButton("电子眼") self.pricing_button = QtWidgets.QPushButton("波动率管理") for button in [ self.market_button, self.greeks_button, self.chain_button, self.manual_button, self.volatility_button, self.hedge_button, self.scenario_button, self.eye_button, self.pricing_button ]: button.setEnabled(False) hbox = QtWidgets.QHBoxLayout() hbox.addWidget(QtWidgets.QLabel("期权产品")) hbox.addWidget(self.portfolio_combo) hbox.addWidget(self.portfolio_button) hbox.addWidget(self.market_button) hbox.addWidget(self.greeks_button) hbox.addWidget(self.manual_button) hbox.addWidget(self.chain_button) hbox.addWidget(self.volatility_button) hbox.addWidget(self.hedge_button) hbox.addWidget(self.scenario_button) hbox.addWidget(self.pricing_button) hbox.addWidget(self.eye_button) self.setLayout(hbox) def register_event(self) -> None: """""" self.signal_new_portfolio.connect(self.process_new_portfolio_event) self.event_engine.register(EVENT_OPTION_NEW_PORTFOLIO, self.signal_new_portfolio.emit) def process_new_portfolio_event(self, event: Event) -> None: """""" self.update_portfolio_combo() def update_portfolio_combo(self) -> None: """""" if not self.portfolio_combo.isEnabled(): return self.portfolio_combo.clear() portfolio_names = self.option_engine.get_portfolio_names() self.portfolio_combo.addItems(portfolio_names) def open_portfolio_dialog(self) -> None: """""" portfolio_name = self.portfolio_combo.currentText() if not portfolio_name: return self.portfolio_name = portfolio_name dialog = PortfolioDialog(self.option_engine, portfolio_name) result = dialog.exec_() if result == dialog.Accepted: self.portfolio_combo.setEnabled(False) self.portfolio_button.setEnabled(False) self.init_widgets() def init_widgets(self) -> None: """""" self.market_monitor = OptionMarketMonitor(self.option_engine, self.portfolio_name) self.greeks_monitor = OptionGreeksMonitor(self.option_engine, self.portfolio_name) self.volatility_chart = OptionVolatilityChart(self.option_engine, self.portfolio_name) self.chain_monitor = OptionChainMonitor(self.option_engine, self.portfolio_name) self.manual_trader = OptionManualTrader(self.option_engine, self.portfolio_name) self.hedge_widget = OptionHedgeWidget(self.option_engine, self.portfolio_name) self.scenario_chart = ScenarioAnalysisChart(self.option_engine, self.portfolio_name) self.eye_manager = ElectronicEyeManager(self.option_engine, self.portfolio_name) self.pricing_manager = PricingVolatilityManager( self.option_engine, self.portfolio_name) self.market_monitor.itemDoubleClicked.connect( self.manual_trader.update_symbol) self.market_button.clicked.connect(self.market_monitor.show) self.greeks_button.clicked.connect(self.greeks_monitor.show) self.manual_button.clicked.connect(self.manual_trader.show) self.chain_button.clicked.connect(self.chain_monitor.show) self.volatility_button.clicked.connect(self.volatility_chart.show) self.scenario_button.clicked.connect(self.scenario_chart.show) self.hedge_button.clicked.connect(self.hedge_widget.show) self.eye_button.clicked.connect(self.eye_manager.show) self.pricing_button.clicked.connect(self.pricing_manager.show) for button in [ self.market_button, self.greeks_button, self.chain_button, self.manual_button, self.volatility_button, self.scenario_button, self.hedge_button, self.eye_button, self.pricing_button ]: button.setEnabled(True) def closeEvent(self, event: QtGui.QCloseEvent) -> None: """""" if self.portfolio_name: self.market_monitor.close() self.greeks_monitor.close() self.volatility_chart.close() self.chain_monitor.close() self.manual_trader.close() self.hedge_widget.close() self.scenario_chart.close() self.eye_manager.close() self.pricing_manager.close() event.accept()
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 live trading") # Create widgets self.class_combo = QtWidgets.QComboBox() add_button = QtWidgets.QPushButton("Add strategy") add_button.clicked.connect(self.add_strategy) init_button = QtWidgets.QPushButton("Prepare All") init_button.clicked.connect(self.cta_engine.init_all_strategies) start_button = QtWidgets.QPushButton("Start All") start_button.clicked.connect(self.cta_engine.start_all_strategies) stop_button = QtWidgets.QPushButton("Stop All") stop_button.clicked.connect(self.cta_engine.stop_all_strategies) clear_button = QtWidgets.QPushButton("Clear log") 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()
class OptionManager(QtWidgets.QWidget): """""" signal_new_portfolio = 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.option_engine = main_engine.get_engine(APP_NAME) self.portfolio_name: str = "" self.market_monitor: OptionMarketMonitor = None self.greeks_monitor: OptionGreeksMonitor = None self.docks: List[QtWidgets.QDockWidget] = [] self.init_ui() self.register_event() def init_ui(self): """""" self.setWindowTitle("OptionMaster") self.portfolio_combo = QtWidgets.QComboBox() self.portfolio_combo.setFixedWidth(150) self.update_portfolio_combo() self.portfolio_button = QtWidgets.QPushButton("配置") self.portfolio_button.clicked.connect(self.open_portfolio_dialog) self.market_button = QtWidgets.QPushButton("T型报价") self.greeks_button = QtWidgets.QPushButton("持仓希腊值") self.chain_button = QtWidgets.QPushButton("拟合升贴水") self.manual_button = QtWidgets.QPushButton("快速交易") for button in [ self.market_button, self.greeks_button, self.chain_button, self.manual_button ]: button.setEnabled(False) hbox = QtWidgets.QHBoxLayout() hbox.addWidget(QtWidgets.QLabel("期权产品")) hbox.addWidget(self.portfolio_combo) hbox.addWidget(self.portfolio_button) hbox.addWidget(self.market_button) hbox.addWidget(self.greeks_button) hbox.addWidget(self.manual_button) hbox.addWidget(self.chain_button) self.setLayout(hbox) def register_event(self): """""" self.signal_new_portfolio.connect(self.process_new_portfolio_event) self.event_engine.register(EVENT_OPTION_NEW_PORTFOLIO, self.signal_new_portfolio.emit) def process_new_portfolio_event(self, event: Event): """""" self.update_portfolio_combo() def update_portfolio_combo(self): """""" if not self.portfolio_combo.isEnabled(): return self.portfolio_combo.clear() portfolio_names = self.option_engine.get_portfolio_names() self.portfolio_combo.addItems(portfolio_names) def open_portfolio_dialog(self): """""" portfolio_name = self.portfolio_combo.currentText() if not portfolio_name: return self.portfolio_name = portfolio_name dialog = PortfolioDialog(self.option_engine, portfolio_name) result = dialog.exec_() if result == dialog.Accepted: self.portfolio_combo.setEnabled(False) self.portfolio_button.setEnabled(False) self.init_widgets() def init_widgets(self): """""" self.market_monitor = OptionMarketMonitor(self.option_engine, self.portfolio_name) self.greeks_monitor = OptionGreeksMonitor(self.option_engine, self.portfolio_name) self.manual_trader = OptionManualTrader(self.option_engine, self.portfolio_name) self.market_monitor.itemDoubleClicked.connect(self.manual_trader.update_symbol) self.market_button.clicked.connect(self.market_monitor.showMaximized) self.greeks_button.clicked.connect(self.greeks_monitor.showMaximized) self.manual_button.clicked.connect(self.manual_trader.show) self.chain_button.clicked.connect(self.calculate_underlying_adjustment) for button in [ self.market_button, self.greeks_button, self.chain_button, self.manual_button ]: button.setEnabled(True) def calculate_underlying_adjustment(self): """""" portfolio = self.option_engine.get_portfolio(self.portfolio_name) for chain in portfolio.chains.values(): chain.calculate_underlying_adjustment()
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) # Code Editor self.editor = CodeEditor(self.main_engine, self.event_engine) 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(), ) start = DB_TZ.localize(start) end = datetime( end_date.year(), end_date.month(), end_date.day(), 23, 59, 59, ) end = DB_TZ.localize(end) 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 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 OptionMarketMonitor(MonitorTable): """""" signal_tick = QtCore.pyqtSignal(Event) signal_trade = QtCore.pyqtSignal(Event) signal_position = QtCore.pyqtSignal(Event) headers: List[Dict] = [ {"name": "symbol", "display": "代码", "cell": MonitorCell}, {"name": "theo_vega", "display": "Vega", "cell": GreeksCell}, {"name": "theo_theta", "display": "Theta", "cell": GreeksCell}, {"name": "theo_gamma", "display": "Gamma", "cell": GreeksCell}, {"name": "theo_delta", "display": "Delta", "cell": GreeksCell}, {"name": "open_interest", "display": "持仓量", "cell": MonitorCell}, {"name": "volume", "display": "成交量", "cell": MonitorCell}, {"name": "bid_impv", "display": "买隐波", "cell": BidCell}, {"name": "bid_volume", "display": "买量", "cell": BidCell}, {"name": "bid_price", "display": "买价", "cell": BidCell}, {"name": "ask_price", "display": "卖价", "cell": AskCell}, {"name": "ask_volume", "display": "卖量", "cell": AskCell}, {"name": "ask_impv", "display": "卖隐波", "cell": AskCell}, {"name": "net_pos", "display": "净持仓", "cell": PosCell}, ] def __init__(self, option_engine: OptionEngine, portfolio_name: str): """""" super().__init__() self.option_engine = option_engine self.event_engine = option_engine.event_engine self.portfolio_name = portfolio_name self.cells: Dict[str, Dict] = {} self.option_symbols: Set[str] = set() self.underlying_option_map: Dict[str, List] = defaultdict(list) self.init_ui() self.register_event() def init_ui(self) -> None: """""" self.setWindowTitle("T型报价") self.verticalHeader().setVisible(False) self.setEditTriggers(self.NoEditTriggers) # Store option and underlying symbols portfolio = self.option_engine.get_portfolio(self.portfolio_name) for option in portfolio.options.values(): self.option_symbols.add(option.vt_symbol) self.underlying_option_map[option.underlying.vt_symbol].append( option.vt_symbol) # Set table row and column numbers row_count = 0 for chain in portfolio.chains.values(): row_count += (1 + len(chain.indexes)) self.setRowCount(row_count) column_count = len(self.headers) * 2 + 1 self.setColumnCount(column_count) call_labels = [d["display"] for d in self.headers] put_labels = copy(call_labels) put_labels.reverse() labels = call_labels + ["行权价"] + put_labels self.setHorizontalHeaderLabels(labels) # Init cells strike_column = len(self.headers) current_row = 0 chain_symbols = list(portfolio.chains.keys()) chain_symbols.sort() for chain_symbol in chain_symbols: chain = portfolio.get_chain(chain_symbol) self.setItem( current_row, strike_column, IndexCell(chain.chain_symbol.split(".")[0]) ) for index in chain.indexes: call = chain.calls[index] put = chain.puts[index] current_row += 1 # Call cells call_cells = {} for column, d in enumerate(self.headers): value = getattr(call, d["name"], "") cell = d["cell"]( text=str(value), vt_symbol=call.vt_symbol ) self.setItem(current_row, column, cell) call_cells[d["name"]] = cell self.cells[call.vt_symbol] = call_cells # Put cells put_cells = {} put_headers = copy(self.headers) put_headers.reverse() for column, d in enumerate(put_headers): column += (strike_column + 1) value = getattr(put, d["name"], "") cell = d["cell"]( text=str(value), vt_symbol=put.vt_symbol ) self.setItem(current_row, column, cell) put_cells[d["name"]] = cell self.cells[put.vt_symbol] = put_cells # Strike cell index_cell = IndexCell(str(call.chain_index)) self.setItem(current_row, strike_column, index_cell) # Move to next row current_row += 1 def register_event(self) -> None: """""" self.signal_tick.connect(self.process_tick_event) self.signal_trade.connect(self.process_trade_event) self.signal_position.connect(self.process_position_event) self.event_engine.register(EVENT_TICK, self.signal_tick.emit) self.event_engine.register(EVENT_TRADE, self.signal_trade.emit) self.event_engine.register(EVENT_POSITION, self.signal_position.emit) def process_tick_event(self, event: Event) -> None: """""" tick = event.data if tick.vt_symbol in self.option_symbols: self.update_price(tick.vt_symbol) self.update_impv(tick.vt_symbol) elif tick.vt_symbol in self.underlying_option_map: option_symbols = self.underlying_option_map[tick.vt_symbol] for vt_symbol in option_symbols: self.update_impv(vt_symbol) self.update_greeks(vt_symbol) def process_trade_event(self, event: Event) -> None: """""" trade = event.data self.update_pos(trade.vt_symbol) def process_position_event(self, event: Event) -> None: """""" position = event.data self.update_pos(position.vt_symbol) def update_pos(self, vt_symbol: str) -> None: """""" option_cells = self.cells.get(vt_symbol, None) if not option_cells: return option = self.option_engine.get_instrument(vt_symbol) option_cells["net_pos"].setText(str(option.net_pos)) def update_price(self, vt_symbol: str) -> None: """""" option_cells = self.cells.get(vt_symbol, None) if not option_cells: return option = self.option_engine.get_instrument(vt_symbol) tick = option.tick option_cells["bid_price"].setText(str(tick.bid_price_1)) option_cells["bid_volume"].setText(str(tick.bid_volume_1)) option_cells["ask_price"].setText(str(tick.ask_price_1)) option_cells["ask_volume"].setText(str(tick.ask_volume_1)) option_cells["volume"].setText(str(tick.volume)) option_cells["open_interest"].setText(str(tick.open_interest)) def update_impv(self, vt_symbol: str) -> None: """""" option_cells = self.cells.get(vt_symbol, None) if not option_cells: return option = self.option_engine.get_instrument(vt_symbol) option_cells["bid_impv"].setText(f"{option.bid_impv * 100:.2f}") option_cells["ask_impv"].setText(f"{option.ask_impv * 100:.2f}") def update_greeks(self, vt_symbol: str) -> None: """""" option_cells = self.cells.get(vt_symbol, None) if not option_cells: return option = self.option_engine.get_instrument(vt_symbol) option_cells["theo_delta"].setText(f"{option.theo_delta:.0f}") option_cells["theo_gamma"].setText(f"{option.theo_gamma:.0f}") option_cells["theo_theta"].setText(f"{option.theo_theta:.0f}") option_cells["theo_vega"].setText(f"{option.theo_vega:.0f}")
class HedgeManager(QtWidgets.QWidget): signal_log = QtCore.pyqtSignal(Event) def __init__(self, option_engine: OptionEngineExt): super().__init__() self.option_engine = option_engine self.main_engine = option_engine.main_engine self.event_engine = option_engine.event_engine self.hedge_engine = option_engine.hedge_engine self.hedge_engine.init_engine() self.init_ui() self.register_event() def init_ui(self) -> None: self.setWindowTitle("Delta对冲") self.setMaximumSize(1440, 800) self.hedge_monitor = HedgeMonitor(self.option_engine) self.strategy_order_monitor = StrategyOrderMonitor( self.main_engine, self.event_engine) self.log_monitor = QtWidgets.QTextEdit() self.log_monitor.setReadOnly(True) self.log_monitor.setMaximumWidth(300) start_hedge_button = QtWidgets.QPushButton("全部启动") start_hedge_button.clicked.connect(self.start_for_all) stop_hedge_button = QtWidgets.QPushButton("全部停止") stop_hedge_button.clicked.connect(self.stop_for_all) self.offset_percent = OffsetPercentSpinBox() self.hedge_percent = HedgePercentSpinBox() offset_percent_btn = QtWidgets.QPushButton("设置") offset_percent_btn.clicked.connect(self.set_offset_percent) hedge_percent_btn = QtWidgets.QPushButton("设置") hedge_percent_btn.clicked.connect(self.set_hedge_percent) QLabel = QtWidgets.QLabel grid = QtWidgets.QGridLayout() grid.addWidget(QLabel("偏移比例"), 0, 0) grid.addWidget(self.offset_percent, 0, 1) grid.addWidget(offset_percent_btn, 0, 2) grid.addWidget(QLabel("对冲比例"), 1, 0) grid.addWidget(self.hedge_percent, 1, 1) grid.addWidget(hedge_percent_btn, 1, 2) left_vbox = QtWidgets.QVBoxLayout() left_vbox.addWidget(self.hedge_monitor) left_vbox.addWidget(self.strategy_order_monitor) ctrl_btn_hbox = QtWidgets.QHBoxLayout() ctrl_btn_hbox.addWidget(start_hedge_button) ctrl_btn_hbox.addWidget(stop_hedge_button) right_vbox = QtWidgets.QVBoxLayout() right_vbox.addLayout(ctrl_btn_hbox) right_vbox.addLayout(grid) right_vbox.addWidget(self.log_monitor) hbox = QtWidgets.QHBoxLayout() hbox.addLayout(left_vbox) hbox.addLayout(right_vbox) self.setLayout(hbox) def register_event(self) -> None: """""" self.signal_log.connect(self.process_log_event) self.event_engine.register(EVENT_OPTION_HEDGE_ALGO_LOG, self.signal_log.emit) def process_log_event(self, event: Event) -> None: """""" log = event.data timestr = log.time.strftime("%H:%M:%S") msg = f"{timestr} {log.msg}" self.log_monitor.append(msg) def show(self) -> None: """""" self.hedge_engine.init_engine() self.hedge_monitor.resizeColumnsToContents() super().showMaximized() def start_for_all(self) -> None: for chain_symbol in self.hedge_monitor.cells.keys(): self.hedge_monitor.start_auto_hedge(chain_symbol) def stop_for_all(self) -> None: self.hedge_engine.stop_all_auto_hedge() def set_offset_percent(self) -> None: offset_percent = self.offset_percent.get_display_value() for cells in self.hedge_monitor.cells.values(): if cells['offset_percent'].isEnabled(): cells['offset_percent'].setValue(offset_percent) def set_hedge_percent(self) -> None: hedge_percent = self.hedge_percent.get_display_value() for cells in self.hedge_monitor.cells.values(): if cells['hedge_percent'].isEnabled(): cells['hedge_percent'].setValue(hedge_percent) def close(self) -> None: self.hedge_engine.save_setting() self.hedge_engine.save_data()
class PricingVolatilityManager(QtWidgets.QWidget): """""" signal_timer = QtCore.pyqtSignal(Event) def __init__(self, option_engine: OptionEngine, portfolio_name: str): """""" super().__init__() self.option_engine = option_engine self.event_engine = option_engine.event_engine self.portfolio = option_engine.get_portfolio(portfolio_name) self.cells: Dict[Tuple, Dict] = {} self.chain_symbols: List[str] = [] self.chain_atm_index: Dict[str, str] = {} self.init_ui() self.register_event() def init_ui(self) -> None: """""" self.setWindowTitle("波动率管理") tab = QtWidgets.QTabWidget() vbox = QtWidgets.QVBoxLayout() vbox.addWidget(tab) self.setLayout(vbox) self.chain_symbols = list(self.portfolio.chains.keys()) self.chain_symbols.sort() for chain_symbol in self.chain_symbols: chain = self.portfolio.get_chain(chain_symbol) table = QtWidgets.QTableWidget() table.setEditTriggers(table.NoEditTriggers) table.verticalHeader().setVisible(False) table.setColumnCount(4) table.setRowCount(len(chain.indexes)) table.setHorizontalHeaderLabels(["行权价", "中值隐波", "定价隐波", "执行拟合"]) table.horizontalHeader().setSectionResizeMode( QtWidgets.QHeaderView.Stretch) for row, index in enumerate(chain.indexes): index_cell = IndexCell(index) mid_impv_cell = MonitorCell("") set_func = partial(self.set_pricing_impv, chain_symbol=chain_symbol, index=index) pricing_impv_spin = VolatilityDoubleSpinBox() pricing_impv_spin.setAlignment(QtCore.Qt.AlignCenter) pricing_impv_spin.valueChanged.connect(set_func) check = QtWidgets.QCheckBox() check_hbox = QtWidgets.QHBoxLayout() check_hbox.setAlignment(QtCore.Qt.AlignCenter) check_hbox.addWidget(check) check_widget = QtWidgets.QWidget() check_widget.setLayout(check_hbox) table.setItem(row, 0, index_cell) table.setItem(row, 1, mid_impv_cell) table.setCellWidget(row, 2, pricing_impv_spin) table.setCellWidget(row, 3, check_widget) cells = { "mid_impv": mid_impv_cell, "pricing_impv": pricing_impv_spin, "check": check } self.cells[(chain_symbol, index)] = cells reset_func = partial(self.reset_pricing_impv, chain_symbol=chain_symbol) button_reset = QtWidgets.QPushButton("重置") button_reset.clicked.connect(reset_func) fit_func = partial(self.fit_pricing_impv, chain_symbol=chain_symbol) button_fit = QtWidgets.QPushButton("拟合") button_fit.clicked.connect(fit_func) increase_func = partial(self.increase_pricing_impv, chain_symbol=chain_symbol) button_increase = QtWidgets.QPushButton("+0.1%") button_increase.clicked.connect(increase_func) decrease_func = partial(self.decrease_pricing_impv, chain_symbol=chain_symbol) button_decrease = QtWidgets.QPushButton("-0.1%") button_decrease.clicked.connect(decrease_func) hbox = QtWidgets.QHBoxLayout() hbox.addWidget(button_reset) hbox.addWidget(button_fit) hbox.addWidget(button_increase) hbox.addWidget(button_decrease) vbox = QtWidgets.QVBoxLayout() vbox.addLayout(hbox) vbox.addWidget(table) chain_widget = QtWidgets.QWidget() chain_widget.setLayout(vbox) tab.addTab(chain_widget, chain_symbol) self.update_pricing_impv(chain_symbol) self.default_foreground = mid_impv_cell.foreground() self.default_background = mid_impv_cell.background() table.resizeRowsToContents() def register_event(self) -> None: """""" self.signal_timer.connect(self.process_timer_event) self.event_engine.register(EVENT_TIMER, self.signal_timer.emit) def process_timer_event(self, event: Event) -> None: """""" for chain_symbol in self.chain_symbols: self.update_mid_impv(chain_symbol) def reset_pricing_impv(self, chain_symbol: str) -> None: """ Set pricing impv to the otm mid impv of each strike price. """ chain = self.portfolio.get_chain(chain_symbol) atm_index = chain.atm_index for index in chain.indexes: call = chain.calls[index] put = chain.puts[index] if index >= atm_index: otm = call else: otm = put call.pricing_impv = otm.mid_impv put.pricing_impv = otm.mid_impv self.update_pricing_impv(chain_symbol) def fit_pricing_impv(self, chain_symbol: str) -> None: """ Fit pricing impv with cubic spline algo. """ chain = self.portfolio.get_chain(chain_symbol) atm_index = chain.atm_index strike_prices = [] pricing_impvs = [] for index in chain.indexes: call = chain.calls[index] put = chain.puts[index] cells = self.cells[(chain_symbol, index)] if not cells["check"].isChecked(): if index >= atm_index: otm = call else: otm = put strike_prices.append(otm.strike_price) pricing_impvs.append(otm.pricing_impv) cs = interpolate.CubicSpline(strike_prices, pricing_impvs) for index in chain.indexes: call = chain.calls[index] put = chain.puts[index] new_impv = float(cs(call.strike_price)) call.pricing_impv = new_impv put.pricing_impv = new_impv self.update_pricing_impv(chain_symbol) def increase_pricing_impv(self, chain_symbol: str) -> None: """ Increase pricing impv of all options within a chain by 0.1%. """ chain = self.portfolio.get_chain(chain_symbol) for option in chain.options.values(): option.pricing_impv += 0.001 self.update_pricing_impv(chain_symbol) def decrease_pricing_impv(self, chain_symbol: str) -> None: """ Decrease pricing impv of all options within a chain by 0.1%. """ chain = self.portfolio.get_chain(chain_symbol) for option in chain.options.values(): option.pricing_impv -= 0.001 self.update_pricing_impv(chain_symbol) def set_pricing_impv(self, value: float, chain_symbol: str, index: str) -> None: """""" new_impv = value / 100 chain = self.portfolio.get_chain(chain_symbol) call = chain.calls[index] call.pricing_impv = new_impv put = chain.puts[index] put.pricing_impv = new_impv def update_pricing_impv(self, chain_symbol: str) -> None: """""" chain = self.portfolio.get_chain(chain_symbol) atm_index = chain.atm_index for index in chain.indexes: if index >= atm_index: otm = chain.calls[index] else: otm = chain.puts[index] value = round(otm.pricing_impv * 100, 1) cells = self.cells[(chain_symbol, index)] cells["pricing_impv"].setValue(value) def update_mid_impv(self, chain_symbol: str) -> None: """""" chain = self.portfolio.get_chain(chain_symbol) atm_index = chain.atm_index for index in chain.indexes: if index >= atm_index: otm = chain.calls[index] else: otm = chain.puts[index] cells = self.cells[(chain_symbol, index)] cells["mid_impv"].setText(f"{otm.mid_impv:.1%}") current_atm_index = self.chain_atm_index.get(chain_symbol, "") if current_atm_index == atm_index: return self.chain_atm_index[chain_symbol] = atm_index if current_atm_index: old_cells = self.cells[(chain_symbol, current_atm_index)] old_cells["mid_impv"].setForeground(self.default_foreground) old_cells["mid_impv"].setBackground(self.default_background) new_cells = self.cells[(chain_symbol, atm_index)] new_cells["mid_impv"].setForeground(COLOR_BLACK) new_cells["mid_impv"].setBackground(COLOR_WHITE)
class ElectronicEyeManager(QtWidgets.QWidget): """""" signal_log = QtCore.pyqtSignal(Event) def __init__(self, option_engine: OptionEngine, portfolio_name: str): """""" super().__init__() self.option_engine = option_engine self.event_Engine = option_engine.event_engine self.algo_engine = option_engine.algo_engine self.portfolio_name = portfolio_name self.init_ui() self.register_event() def init_ui(self) -> None: """""" self.setWindowTitle("期权电子眼") self.algo_monitor = ElectronicEyeMonitor(self.option_engine, self.portfolio_name) self.log_monitor = QtWidgets.QTextEdit() self.log_monitor.setReadOnly(True) self.log_monitor.setMaximumWidth(400) stop_pricing_button = QtWidgets.QPushButton("停止定价") stop_pricing_button.clicked.connect(self.stop_pricing_for_all) stop_trading_button = QtWidgets.QPushButton("停止交易") stop_trading_button.clicked.connect(self.stop_trading_for_all) self.price_spread_spin = AlgoDoubleSpinBox() self.volatility_spread_spin = AlgoDoubleSpinBox() self.direction_combo = AlgoDirectionCombo() self.max_order_size_spin = AlgoPositiveSpinBox() self.target_pos_spin = AlgoSpinBox() self.max_pos_spin = AlgoPositiveSpinBox() price_spread_button = QtWidgets.QPushButton("设置") price_spread_button.clicked.connect(self.set_price_spread_for_all) volatility_spread_button = QtWidgets.QPushButton("设置") volatility_spread_button.clicked.connect( self.set_volatility_spread_for_all) direction_button = QtWidgets.QPushButton("设置") direction_button.clicked.connect(self.set_direction_for_all) max_order_size_button = QtWidgets.QPushButton("设置") max_order_size_button.clicked.connect(self.set_max_order_size_for_all) target_pos_button = QtWidgets.QPushButton("设置") target_pos_button.clicked.connect(self.set_target_pos_for_all) max_pos_button = QtWidgets.QPushButton("设置") max_pos_button.clicked.connect(self.set_max_pos_for_all) QLabel = QtWidgets.QLabel grid = QtWidgets.QGridLayout() grid.addWidget(QLabel("价格价差"), 0, 0) grid.addWidget(self.price_spread_spin, 0, 1) grid.addWidget(price_spread_button, 0, 2) grid.addWidget(QLabel("隐波价差"), 1, 0) grid.addWidget(self.volatility_spread_spin, 1, 1) grid.addWidget(volatility_spread_button, 1, 2) grid.addWidget(QLabel("持仓上限"), 2, 0) grid.addWidget(self.max_pos_spin, 2, 1) grid.addWidget(max_pos_button, 2, 2) grid.addWidget(QLabel("目标持仓"), 3, 0) grid.addWidget(self.target_pos_spin, 3, 1) grid.addWidget(target_pos_button, 3, 2) grid.addWidget(QLabel("最大委托"), 4, 0) grid.addWidget(self.max_order_size_spin, 4, 1) grid.addWidget(max_order_size_button, 4, 2) grid.addWidget(QLabel("方向"), 5, 0) grid.addWidget(self.direction_combo, 5, 1) grid.addWidget(direction_button, 5, 2) hbox1 = QtWidgets.QHBoxLayout() hbox1.addWidget(stop_pricing_button) hbox1.addWidget(stop_trading_button) vbox = QtWidgets.QVBoxLayout() vbox.addLayout(hbox1) vbox.addLayout(grid) vbox.addWidget(self.log_monitor) hbox = QtWidgets.QHBoxLayout() hbox.addWidget(self.algo_monitor) hbox.addLayout(vbox) self.setLayout(hbox) def register_event(self) -> None: """""" self.signal_log.connect(self.process_log_event) self.event_Engine.register(EVENT_OPTION_ALGO_LOG, self.signal_log.emit) def process_log_event(self, event: Event) -> None: """""" log = event.data timestr = log.time.strftime("%H:%M:%S") msg = f"{timestr} {log.msg}" self.log_monitor.append(msg) def show(self) -> None: """""" self.algo_engine.init_engine(self.portfolio_name) self.algo_monitor.resizeColumnsToContents() super().showMaximized() def set_price_spread_for_all(self) -> None: """""" price_spread = self.price_spread_spin.get_value() for cells in self.algo_monitor.cells.values(): if cells["price_spread"].isEnabled(): cells["price_spread"].setValue(price_spread) def set_volatility_spread_for_all(self) -> None: """""" volatility_spread = self.volatility_spread_spin.get_value() for cells in self.algo_monitor.cells.values(): if cells["volatility_spread"].isEnabled(): cells["volatility_spread"].setValue(volatility_spread) def set_direction_for_all(self) -> None: """""" ix = self.direction_combo.currentIndex() for cells in self.algo_monitor.cells.values(): if cells["direction"].isEnabled(): cells["direction"].setCurrentIndex(ix) def set_max_order_size_for_all(self) -> None: """""" size = self.max_order_size_spin.get_value() for cells in self.algo_monitor.cells.values(): if cells["max_order_size"].isEnabled(): cells["max_order_size"].setValue(size) def set_target_pos_for_all(self) -> None: """""" pos = self.target_pos_spin.get_value() for cells in self.algo_monitor.cells.values(): if cells["target_pos"].isEnabled(): cells["target_pos"].setValue(pos) def set_max_pos_for_all(self) -> None: """""" pos = self.max_pos_spin.get_value() for cells in self.algo_monitor.cells.values(): if cells["max_pos"].isEnabled(): cells["max_pos"].setValue(pos) def stop_pricing_for_all(self) -> None: """""" for vt_symbol in self.algo_monitor.cells.keys(): self.algo_monitor.stop_algo_pricing(vt_symbol) def stop_trading_for_all(self) -> None: """""" for vt_symbol in self.algo_monitor.cells.keys(): self.algo_monitor.stop_algo_trading(vt_symbol)
class ElectronicEyeMonitor(QtWidgets.QTableWidget): """""" signal_tick = QtCore.pyqtSignal(Event) signal_pricing = QtCore.pyqtSignal(Event) signal_status = QtCore.pyqtSignal(Event) signal_trading = QtCore.pyqtSignal(Event) headers: List[Dict] = [ { "name": "bid_volume", "display": "买量", "cell": BidCell }, { "name": "bid_price", "display": "买价", "cell": BidCell }, { "name": "ask_price", "display": "卖价", "cell": AskCell }, { "name": "ask_volume", "display": "卖量", "cell": AskCell }, { "name": "algo_bid_price", "display": "目标\n买价", "cell": BidCell }, { "name": "algo_ask_price", "display": "目标\n卖价", "cell": AskCell }, { "name": "algo_spread", "display": "价差", "cell": MonitorCell }, { "name": "ref_price", "display": "理论价", "cell": MonitorCell }, { "name": "pricing_impv", "display": "定价\n隐波", "cell": MonitorCell }, { "name": "net_pos", "display": "净持仓", "cell": PosCell }, { "name": "price_spread", "display": "价格\n价差", "cell": AlgoDoubleSpinBox }, { "name": "volatility_spread", "display": "隐波\n价差", "cell": AlgoDoubleSpinBox }, { "name": "max_pos", "display": "持仓\n上限", "cell": AlgoPositiveSpinBox }, { "name": "target_pos", "display": "目标\n持仓", "cell": AlgoSpinBox }, { "name": "max_order_size", "display": "最大\n委托", "cell": AlgoPositiveSpinBox }, { "name": "direction", "display": "方向", "cell": AlgoDirectionCombo }, { "name": "pricing_active", "display": "定价", "cell": AlgoPricingButton }, { "name": "trading_active", "display": "交易", "cell": AlgoTradingButton }, ] def __init__(self, option_engine: OptionEngine, portfolio_name: str): """""" super().__init__() self.option_engine = option_engine self.event_engine = option_engine.event_engine self.algo_engine = option_engine.algo_engine self.portfolio_name = portfolio_name self.cells: Dict[str, Dict] = {} self.init_ui() self.register_event() def init_ui(self) -> None: """""" self.setWindowTitle("电子眼") self.verticalHeader().setVisible(False) self.setEditTriggers(self.NoEditTriggers) # Set table row and column numbers portfolio = self.option_engine.get_portfolio(self.portfolio_name) row_count = 0 for chain in portfolio.chains.values(): row_count += (1 + len(chain.indexes)) self.setRowCount(row_count) column_count = len(self.headers) * 2 + 1 self.setColumnCount(column_count) call_labels = [d["display"] for d in self.headers] put_labels = copy(call_labels) put_labels.reverse() labels = call_labels + ["行权价"] + put_labels self.setHorizontalHeaderLabels(labels) # Init cells strike_column = len(self.headers) current_row = 0 chain_symbols = list(portfolio.chains.keys()) chain_symbols.sort() for chain_symbol in chain_symbols: chain = portfolio.get_chain(chain_symbol) self.setItem(current_row, strike_column, IndexCell(chain.chain_symbol.split(".")[0])) for index in chain.indexes: call = chain.calls[index] put = chain.puts[index] current_row += 1 # Call cells call_cells = {} for column, d in enumerate(self.headers): cell_type = d["cell"] if issubclass(cell_type, QtWidgets.QPushButton): cell = cell_type(call.vt_symbol, self) else: cell = cell_type() call_cells[d["name"]] = cell if isinstance(cell, QtWidgets.QTableWidgetItem): self.setItem(current_row, column, cell) else: self.setCellWidget(current_row, column, cell) self.cells[call.vt_symbol] = call_cells # Put cells put_cells = {} put_headers = copy(self.headers) put_headers.reverse() for column, d in enumerate(put_headers): column += (strike_column + 1) cell_type = d["cell"] if issubclass(cell_type, QtWidgets.QPushButton): cell = cell_type(put.vt_symbol, self) else: cell = cell_type() put_cells[d["name"]] = cell if isinstance(cell, QtWidgets.QTableWidgetItem): self.setItem(current_row, column, cell) else: self.setCellWidget(current_row, column, cell) self.cells[put.vt_symbol] = put_cells # Strike cell index_cell = IndexCell(str(call.chain_index)) self.setItem(current_row, strike_column, index_cell) # Move to next row current_row += 1 self.resizeColumnsToContents() def register_event(self) -> None: """""" self.signal_pricing.connect(self.process_pricing_event) self.signal_trading.connect(self.process_trading_event) self.signal_status.connect(self.process_status_event) self.signal_tick.connect(self.process_tick_event) self.event_engine.register(EVENT_OPTION_ALGO_PRICING, self.signal_pricing.emit) self.event_engine.register(EVENT_OPTION_ALGO_TRADING, self.signal_trading.emit) self.event_engine.register(EVENT_OPTION_ALGO_STATUS, self.signal_status.emit) self.event_engine.register(EVENT_TICK, self.signal_tick.emit) def process_tick_event(self, event: Event) -> None: """""" tick = event.data cells = self.cells.get(tick.vt_symbol, None) if not cells: return cells["bid_price"].setText(str(tick.bid_price_1)) cells["ask_price"].setText(str(tick.ask_price_1)) cells["bid_volume"].setText(str(tick.bid_volume_1)) cells["ask_volume"].setText(str(tick.ask_volume_1)) def process_status_event(self, event: Event) -> None: """""" algo = event.data cells = self.cells[algo.vt_symbol] cells["price_spread"].update_status(algo.pricing_active) cells["volatility_spread"].update_status(algo.pricing_active) cells["pricing_active"].update_status(algo.pricing_active) cells["max_pos"].update_status(algo.trading_active) cells["target_pos"].update_status(algo.trading_active) cells["max_order_size"].update_status(algo.trading_active) cells["direction"].update_status(algo.trading_active) cells["trading_active"].update_status(algo.trading_active) def process_pricing_event(self, event: Event) -> None: """""" algo = event.data cells = self.cells[algo.vt_symbol] if algo.ref_price: cells["algo_bid_price"].setText(str(algo.algo_bid_price)) cells["algo_ask_price"].setText(str(algo.algo_ask_price)) cells["algo_spread"].setText(str(algo.algo_spread)) cells["ref_price"].setText(str(algo.ref_price)) cells["pricing_impv"].setText(f"{algo.pricing_impv * 100:.2f}") else: cells["algo_bid_price"].setText("") cells["algo_ask_price"].setText("") cells["algo_spread"].setText("") cells["ref_price"].setText("") cells["pricing_impv"].setText("") def process_trading_event(self, event: Event) -> None: """""" algo = event.data cells = self.cells[algo.vt_symbol] if algo.trading_active: cells["net_pos"].setText(str(algo.option.net_pos)) else: cells["net_pos"].setText("") def process_position_event(self, event: Event) -> None: """""" algo = event.data cells = self.cells[algo.vt_symbol] cells["net_pos"].setText(str(algo.option.net_pos)) def start_algo_pricing(self, vt_symbol: str) -> None: """""" cells = self.cells[vt_symbol] params = {} params["price_spread"] = cells["price_spread"].get_value() params["volatility_spread"] = cells["volatility_spread"].get_value() / \ 100 self.algo_engine.start_algo_pricing(vt_symbol, params) def stop_algo_pricing(self, vt_symbol: str) -> None: """""" self.algo_engine.stop_algo_pricing(vt_symbol) def start_algo_trading(self, vt_symbol: str) -> None: """""" cells = self.cells[vt_symbol] params = cells["direction"].get_value() for name in ["max_pos", "target_pos", "max_order_size"]: params[name] = cells[name].get_value() self.algo_engine.start_algo_trading(vt_symbol, params) def stop_algo_trading(self, vt_symbol: str) -> None: """""" self.algo_engine.stop_algo_trading(vt_symbol)
class SignalMonitor(QtWidgets.QTableWidget): """""" signal = QtCore.pyqtSignal(Event) def __init__(self, radar_engine: RadarEngine): """""" super().__init__() self.radar_engine: RadarEngine = radar_engine self.event_engine: EventEngine = radar_engine.event_engine self.cells: Dict[str, Dict[str, RadarCell]] = {} self.init_ui() self.register_event() def init_ui(self) -> None: """""" headers = ["信号编号", "规则名称", "信号类型", "目标数值", "声音通知", "邮件通知", " "] self.setColumnCount(len(headers)) self.setHorizontalHeaderLabels(headers) self.verticalHeader().setVisible(False) self.setEditTriggers(self.NoEditTriggers) self.setAlternatingRowColors(True) h_header = self.horizontalHeader() h_header.setSectionResizeMode(h_header.Stretch) def register_event(self) -> None: """""" self.signal.connect(self.process_event) self.event_engine.register(EVENT_RADAR_SIGNAL, self.signal.emit) def process_event(self, event: Event) -> None: """""" signal: RadarSignal = event.data if signal.signal_id not in self.cells: id_cell = RadarCell(str(signal.signal_id)) name_cell = RadarCell(signal.rule_name) type_cell = RadarCell(signal.signal_type.value) target_cell = RadarCell(str(signal.signal_target)) sound_cell = RadarCell(str(signal.signal_sound)) email_cell = RadarCell(str(signal.signal_email)) remove_func = partial(self.remove_signal, signal.signal_id) remove_button = QtWidgets.QPushButton("删除") remove_button.clicked.connect(remove_func) self.insertRow(0) self.setItem(0, 0, id_cell) self.setItem(0, 1, name_cell) self.setItem(0, 2, type_cell) self.setItem(0, 3, target_cell) self.setItem(0, 4, sound_cell) self.setItem(0, 5, email_cell) self.setCellWidget(0, 6, remove_button) self.cells[signal.signal_id] = id_cell else: id_cell = self.cells[signal.signal_id] if not signal.active: row = self.row(id_cell) self.hideRow(row) def remove_signal(self, signal_id: int) -> None: """""" self.radar_engine.remove_signal(signal_id)
class PortfolioStrategyManager(QtWidgets.QWidget): """""" signal_log = QtCore.pyqtSignal(Event) signal_strategy = QtCore.pyqtSignal(Event) def __init__(self, main_engine: MainEngine, event_engine: EventEngine): """""" super().__init__() self.main_engine: MainEngine = main_engine self.event_engine: EventEngine = event_engine self.strategy_engine: StrategyEngine = main_engine.get_engine(APP_NAME) self.managers: Dict[str, StrategyManager] = {} self.init_ui() self.register_event() self.strategy_engine.init_engine() self.update_class_combo() def init_ui(self) -> None: """""" self.setWindowTitle("组合策略") # 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.strategy_engine.init_all_strategies) start_button = QtWidgets.QPushButton("全部启动") start_button.clicked.connect(self.strategy_engine.start_all_strategies) stop_button = QtWidgets.QPushButton("全部停止") stop_button.clicked.connect(self.strategy_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) # 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) hbox2 = QtWidgets.QHBoxLayout() hbox2.addWidget(scroll_area) hbox2.addWidget(self.log_monitor) vbox = QtWidgets.QVBoxLayout() vbox.addLayout(hbox1) vbox.addLayout(hbox2) self.setLayout(vbox) def update_class_combo(self): """""" self.class_combo.addItems( self.strategy_engine.get_all_strategy_class_names()) def register_event(self): """""" self.signal_strategy.connect(self.process_strategy_event) self.event_engine.register(EVENT_PORTFOLIO_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.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() def add_strategy(self): """""" class_name = str(self.class_combo.currentText()) if not class_name: return parameters = self.strategy_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_symbols = setting.pop("vt_symbols").split(",") strategy_name = setting.pop("strategy_name") self.strategy_engine.add_strategy(class_name, strategy_name, vt_symbols, setting) def clear_log(self): """""" self.log_monitor.setRowCount(0) def show(self): """""" self.showMaximized()
class OptionGreeksMonitor(MonitorTable): """""" signal_tick = QtCore.pyqtSignal(Event) signal_trade = QtCore.pyqtSignal(Event) signal_position = QtCore.pyqtSignal(Event) headers: List[Dict] = [ {"name": "long_pos", "display": "多仓", "cell": PosCell}, {"name": "short_pos", "display": "空仓", "cell": PosCell}, {"name": "net_pos", "display": "净仓", "cell": PosCell}, {"name": "pos_delta", "display": "Delta", "cell": GreeksCell}, {"name": "pos_gamma", "display": "Gamma", "cell": GreeksCell}, {"name": "pos_theta", "display": "Theta", "cell": GreeksCell}, {"name": "pos_vega", "display": "Vega", "cell": GreeksCell} ] ROW_DATA = Union[OptionData, UnderlyingData, ChainData, PortfolioData] def __init__(self, option_engine: OptionEngine, portfolio_name: str): """""" super().__init__() self.option_engine = option_engine self.event_engine = option_engine.event_engine self.portfolio_name = portfolio_name self.cells: Dict[str, Dict] = {} self.option_symbols: Set[str] = set() self.underlying_option_map: Dict[str, List] = defaultdict(list) self.init_ui() self.register_event() def init_ui(self) -> None: """""" self.setWindowTitle("希腊值风险") self.verticalHeader().setVisible(False) self.setEditTriggers(self.NoEditTriggers) # Store option and underlying symbols portfolio = self.option_engine.get_portfolio(self.portfolio_name) for option in portfolio.options.values(): self.option_symbols.add(option.vt_symbol) self.underlying_option_map[option.underlying.vt_symbol].append( option.vt_symbol) # Set table row and column numbers row_count = 1 for chain in portfolio.chains.values(): row_count += (1 + len(chain.indexes) * 2) self.setRowCount(row_count) column_count = len(self.headers) + 2 self.setColumnCount(column_count) labels = ["类别", "代码"] + [d["display"] for d in self.headers] self.setHorizontalHeaderLabels(labels) # Init cells row_names = [self.portfolio_name] row_names.append("") underlying_symbols = list(portfolio.underlyings.keys()) underlying_symbols.sort() row_names.extend(underlying_symbols) row_names.append("") chain_symbols = list(portfolio.chains.keys()) chain_symbols.sort() row_names.extend(chain_symbols) row_names.append("") option_symbols = list(portfolio.options.keys()) option_symbols.sort() row_names.extend(option_symbols) type_map = {} type_map[self.portfolio_name] = "组合" for symbol in underlying_symbols: type_map[symbol] = "标的" for symbol in chain_symbols: type_map[symbol] = "期权链" for symbol in option_symbols: type_map[symbol] = "期权" for row, row_name in enumerate(row_names): if not row_name: continue row_cells = {} type_cell = MonitorCell(type_map[row_name]) self.setItem(row, 0, type_cell) name = row_name.split(".")[0] name_cell = MonitorCell(name) self.setItem(row, 1, name_cell) for column, d in enumerate(self.headers): cell = d["cell"]() self.setItem(row, column + 2, cell) row_cells[d["name"]] = cell self.cells[row_name] = row_cells if row_name != self.portfolio_name: self.hideRow(row) self.resizeColumnToContents(0) def register_event(self) -> None: """""" self.signal_tick.connect(self.process_tick_event) self.signal_trade.connect(self.process_trade_event) self.signal_position.connect(self.process_position_event) self.event_engine.register(EVENT_TICK, self.signal_tick.emit) self.event_engine.register(EVENT_TRADE, self.signal_trade.emit) self.event_engine.register(EVENT_POSITION, self.signal_position.emit) def process_tick_event(self, event: Event) -> None: """""" tick = event.data if tick.vt_symbol not in self.underlying_option_map: return self.update_underlying_tick(tick.vt_symbol) def process_trade_event(self, event: Event) -> None: """""" trade = event.data if trade.vt_symbol not in self.cells: return self.update_pos(trade.vt_symbol) def process_position_event(self, event: Event) -> None: """""" position = event.data if position.vt_symbol not in self.cells: return self.update_pos(position.vt_symbol) def update_underlying_tick(self, vt_symbol: str) -> None: """""" underlying = self.option_engine.get_instrument(vt_symbol) self.update_row(vt_symbol, underlying) for chain in underlying.chains.values(): self.update_row(chain.chain_symbol, chain) for option in chain.options.values(): self.update_row(option.vt_symbol, option) portfolio = underlying.portfolio self.update_row(portfolio.name, portfolio) def update_pos(self, vt_symbol: str) -> None: """""" instrument = self.option_engine.get_instrument(vt_symbol) self.update_row(vt_symbol, instrument) # For option, greeks of chain also needs to be updated. if isinstance(instrument, OptionData): chain = instrument.chain self.update_row(chain.chain_symbol, chain) portfolio = instrument.portfolio self.update_row(portfolio.name, portfolio) def update_row(self, row_name: str, row_data: ROW_DATA) -> None: """""" row_cells = self.cells[row_name] row = self.row(row_cells["long_pos"]) # Hide rows with no existing position if not row_data.long_pos and not row_data.short_pos: if row_name != self.portfolio_name: self.hideRow(row) return self.showRow(row) row_cells["long_pos"].setText(f"{row_data.long_pos}") row_cells["short_pos"].setText(f"{row_data.short_pos}") row_cells["net_pos"].setText(f"{row_data.net_pos}") row_cells["pos_delta"].setText(f"{row_data.pos_delta:.0f}") if not isinstance(row_data, UnderlyingData): row_cells["pos_gamma"].setText(f"{row_data.pos_gamma:.0f}") row_cells["pos_theta"].setText(f"{row_data.pos_theta:.0f}") row_cells["pos_vega"].setText(f"{row_data.pos_vega:.0f}")
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 OptionChainMonitor(MonitorTable): """""" signal_timer = QtCore.pyqtSignal(Event) def __init__(self, option_engine: OptionEngine, portfolio_name: str): """""" super().__init__() self.option_engine = option_engine self.event_engine = option_engine.event_engine self.portfolio_name = portfolio_name self.cells: Dict[str, Dict] = {} self.init_ui() self.register_event() def init_ui(self) -> None: """""" self.setWindowTitle("期权链跟踪") self.verticalHeader().setVisible(False) self.setEditTriggers(self.NoEditTriggers) # Store option and underlying symbols portfolio = self.option_engine.get_portfolio(self.portfolio_name) # Set table row and column numbers self.setRowCount(len(portfolio.chains)) labels = ["期权链", "剩余交易日", "标的物", "升贴水"] self.setColumnCount(len(labels)) self.setHorizontalHeaderLabels(labels) # Init cells chain_symbols = list(portfolio.chains.keys()) chain_symbols.sort() for row, chain_symbol in enumerate(chain_symbols): chain = portfolio.chains[chain_symbol] adjustment_cell = MonitorCell() underlying_cell = MonitorCell() self.setItem(row, 0, MonitorCell(chain.chain_symbol.split(".")[0])) self.setItem(row, 1, MonitorCell(str(chain.days_to_expiry))) self.setItem(row, 2, underlying_cell) self.setItem(row, 3, adjustment_cell) self.cells[chain.chain_symbol] = { "underlying": underlying_cell, "adjustment": adjustment_cell } # Additional table adjustment horizontal_header = self.horizontalHeader() horizontal_header.setSectionResizeMode(horizontal_header.Stretch) def register_event(self) -> None: """""" self.signal_timer.connect(self.process_timer_event) self.event_engine.register(EVENT_TIMER, self.signal_timer.emit) def process_timer_event(self, event: Event) -> None: """""" portfolio = self.option_engine.get_portfolio(self.portfolio_name) for chain in portfolio.chains.values(): underlying: UnderlyingData = chain.underlying underlying_symbol: str = underlying.vt_symbol.split(".")[0] if chain.underlying_adjustment == float("inf"): continue adjustment = round_to( chain.underlying_adjustment, underlying.pricetick ) chain_cells = self.cells[chain.chain_symbol] chain_cells["underlying"].setText(underlying_symbol) chain_cells["adjustment"].setText(str(adjustment))
class HedgeMonitor(QtWidgets.QTableWidget): signal_status = QtCore.pyqtSignal(Event) headers: List[Dict] = [ { "name": "chain_symbol", "display": "期权链", "cell": MonitorCell }, { "name": "balance_price", "display": "中性基准价", "cell": MonitorCell }, { "name": "up_price", "display": "上阈值", "cell": MonitorCell }, { "name": "down_price", "display": "下阈值", "cell": MonitorCell }, { "name": "pos_delta", "display": "Delta", "cell": GreeksCell }, { "name": "net_pos", "display": "组合净仓", "cell": PosCell }, { "name": "offset_percent", "display": "偏移比例", "cell": OffsetPercentSpinBox }, { "name": "hedge_percent", "display": "对冲比例", "cell": HedgePercentSpinBox }, { "name": "status", "display": "状态", "cell": MonitorCell }, { "name": "auto_hedge", "display": "监测开关", "cell": HedgeAutoButton }, { "name": "action_hedge", "display": "对冲", "cell": HedgeActionButton }, ] def __init__(self, option_engine: OptionEngineExt): super().__init__() self.option_engine: OptionEngineExt = option_engine self.event_engine: EventEngine = option_engine.event_engine self.hedge_engine: HedgeEngine = self.option_engine.hedge_engine self.chains: Dict[str, ChainData] = self.hedge_engine.chains self.cells: Dict[str, Dict] = {} self.init_ui() self.register_event() def init_ui(self) -> None: self.setWindowTitle("通道对冲") self.verticalHeader().setVisible(False) self.setEditTriggers(self.NoEditTriggers) chain_symbols = self.chains.keys() self.setRowCount(len(chain_symbols)) self.setColumnCount(len(self.headers)) labels = [d["display"] for d in self.headers] self.setHorizontalHeaderLabels(labels) for row, chain_symbol in enumerate(chain_symbols): chain_cells = {} for column, d in enumerate(self.headers): cell_type = d['cell'] cell_name = d['name'] if cell_name in ['auto_hedge', 'action_hedge']: cell = cell_type(chain_symbol, self) else: cell = cell_type() if isinstance(cell, QtWidgets.QTableWidgetItem): self.setItem(row, column, cell) else: self.setCellWidget(row, column, cell) chain_cells[cell_name] = cell self.cells[chain_symbol] = chain_cells self.resizeColumnsToContents() for chain_symbol in self.cells: algo = self.hedge_engine.hedge_algos.get(chain_symbol) self.update_algo_status(algo) self.update_chain_attr(chain_symbol, 'chain_symbol') self.update_chain_attr(chain_symbol, 'net_pos') self.update_chain_attr(chain_symbol, 'pos_delta') def register_event(self) -> None: self.signal_status.connect(self.process_status_event) self.event_engine.register(EVENT_OPTION_HEDGE_ALGO_STATUS, self.signal_status.emit) def process_status_event(self, event: Event) -> None: algo = event.data self.update_algo_status(algo) def update_algo_status(self, algo: ChannelHedgeAlgo): cells = self.cells[algo.chain_symbol] cells['status'].setText(algo.status.value) cells['balance_price'].setText(f'{algo.balance_price:0.3f}') cells['up_price'].setText(f'{algo.up_price:0.3f}') cells['down_price'].setText(f'{algo.down_price:0.3f}') print('update algo status:', algo.chain.net_pos, algo.chain.pos_delta) cells['net_pos'].setText(str(algo.chain.net_pos)) cells['pos_delta'].setText(f'{algo.chain.pos_delta:0.0f}') cells['auto_hedge'].update_status(algo.is_active()) cells['offset_percent'].setValue(algo.offset_percent * 100) cells['hedge_percent'].setValue(int(algo.hedge_percent * 100)) cells['offset_percent'].update_status(algo.is_active()) cells['hedge_percent'].update_status(algo.is_active()) def update_chain_attr(self, chain_symbol: str, attr_name: str): chain = self.chains.get(chain_symbol) cells = self.cells[chain_symbol] if attr_name in cells: value = getattr(chain, attr_name, None) if value is not None: cells[attr_name].setText(str(value)) def start_auto_hedge(self, chain_symbol) -> None: cells = self.cells[chain_symbol] params = {} for name in ['offset_percent', 'hedge_percent']: params[name] = cells[name].get_real_value() self.hedge_engine.start_hedge_algo(chain_symbol, params) def stop_auto_hedge(self, chain_symbol) -> None: self.hedge_engine.stop_hedge_algo(chain_symbol)
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 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)
class ComboBox(QtWidgets.QComboBox): pop_show = QtCore.pyqtSignal() def showPopup(self): self.pop_show.emit() super(ComboBox, self).showPopup()
class ScriptManager(QtWidgets.QWidget): """""" signal_log = 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.script_engine = main_engine.get_engine(APP_NAME) self.script_path = "" self.init_ui() self.register_event() self.script_engine.init() def init_ui(self): """""" self.setWindowTitle("脚本策略") start_button = QtWidgets.QPushButton("启动") start_button.clicked.connect(self.start_script) stop_button = QtWidgets.QPushButton("停止") stop_button.clicked.connect(self.stop_script) select_button = QtWidgets.QPushButton("打开") select_button.clicked.connect(self.select_script) self.strategy_line = QtWidgets.QLineEdit() self.log_monitor = QtWidgets.QTextEdit() self.log_monitor.setReadOnly(True) clear_button = QtWidgets.QPushButton("清空") clear_button.clicked.connect(self.log_monitor.clear) hbox = QtWidgets.QHBoxLayout() hbox.addWidget(self.strategy_line) hbox.addWidget(select_button) hbox.addWidget(start_button) hbox.addWidget(stop_button) hbox.addStretch() hbox.addWidget(clear_button) vbox = QtWidgets.QVBoxLayout() vbox.addLayout(hbox) vbox.addWidget(self.log_monitor) self.setLayout(vbox) def register_event(self): """""" self.signal_log.connect(self.process_log_event) self.event_engine.register(EVENT_SCRIPT_LOG, self.signal_log.emit) def show(self): """""" self.showMaximized() def process_log_event(self, event: Event): """""" log = event.data msg = f"{log.time}\t{log.msg}" self.log_monitor.append(msg) def start_script(self): """""" if self.script_path: self.script_engine.start_strategy(self.script_path) def stop_script(self): """""" self.script_engine.stop_strategy() def select_script(self): """""" cwd = str(Path.cwd()) path, type_ = QtWidgets.QFileDialog.getOpenFileName( self, u"载入策略脚本", cwd, "Python File(*.py)" ) if path: self.script_path = path self.strategy_line.setText(path)
class FollowManager(QtWidgets.QWidget): signal_log = QtCore.pyqtSignal(Event) # timer = QtCore.QTimer() def __init__(self, main_engine: MainEngine, event_engine: EventEngine): """""" super(FollowManager, self).__init__() self.main_engine = main_engine self.event_engine = event_engine self.follow_engine = main_engine.get_engine(APP_NAME) self.sync_symbol = '' self.is_gateway_inited = False self.init_ui() self.follow_engine.init_engine() self.register_event() def init_ui(self): """""" self.setWindowTitle(f"跟随交易 [{TRADER_DIR}]") self.setMinimumSize(920, 750) self.setMaximumSize(1920, 1080) # create widgets self.start_button = QtWidgets.QPushButton("启动") self.start_button.clicked.connect(self.start_follow) self.stop_button = QtWidgets.QPushButton("停止") self.stop_button.clicked.connect(self.stop_follow) self.stop_button.setEnabled(False) self.sync_pos_button = QtWidgets.QPushButton("同步持仓") self.sync_pos_button.clicked.connect(self.sync_pos) self.sync_pos_button.setEnabled(False) self.modify_pos_button = QtWidgets.QPushButton("修改仓位") self.modify_pos_button.clicked.connect(self.manual_modify_pos) self.set_skip_button = QtWidgets.QPushButton("同步设置") self.set_skip_button.clicked.connect(self.set_skip_contracts) self.set_order_button = QtWidgets.QPushButton("委托设置") self.set_order_button.clicked.connect(self.set_order_setting) self.close_hedged_pos_button = QtWidgets.QPushButton("锁仓单平仓") self.close_hedged_pos_button.clicked.connect(self.close_hedged_pos) self.close_hedged_pos_button.setEnabled(False) for btn in [self.start_button, self.stop_button, self.sync_pos_button, self.modify_pos_button, self.set_skip_button, self.set_order_button, self.close_hedged_pos_button]: btn.setFixedHeight(btn.sizeHint().height() * 2) gateways = self.follow_engine.get_connected_gateway_names() if len(gateways) == 2: self.is_gateway_inited = True self.source_combo = ComboBox() self.source_combo.addItems(gateways) self.source_combo.pop_show.connect(self.refresh_gateway_name) self.target_combo = ComboBox() self.target_combo.addItems(gateways) self.target_combo.pop_show.connect(self.refresh_gateway_name) self.skip_contracts_combo = ComboBox() self.skip_contracts_combo.pop_show.connect(self.refresh_skip_contracts) self.refresh_skip_contracts() self.intraday_combo = ComboBox() self.intraday_combo.pop_show.connect(self.refresh_intraday) self.refresh_intraday() self.order_vol_combo = ComboBox() self.order_vol_combo.pop_show.connect(self.refresh_order_vols) self.refresh_order_vols() self.follow_direction_combo = QtWidgets.QComboBox() self.follow_direction_combo.addItems(['正向跟随', '反向跟随']) self.follow_direction_combo.activated[str].connect(self.set_follow_direction) self.get_current_follow_direction() self.follow_direction_combo.setEnabled(False) self.intraday_trading_combo = QtWidgets.QComboBox() self.intraday_trading_combo.addItems(['是', '否']) self.intraday_trading_combo.activated[str].connect(self.set_is_intraday_trading) self.get_current_intraday_trading() validator = QtGui.QIntValidator() self.follow_timeout_line = QtWidgets.QLineEdit(str(self.follow_engine.filter_trade_timeout)) self.follow_timeout_line.setValidator(validator) self.follow_timeout_line.editingFinished.connect(self.set_follow_timeout) self.multiples_line = QtWidgets.QLineEdit(str(self.follow_engine.multiples)) self.multiples_line.setValidator(validator) self.multiples_line.editingFinished.connect(self.set_multiples) self.pos_delta_monitor = PosDeltaMonitor(self.main_engine, self.event_engine) self.log_monitor = LogMonitor(self.main_engine, self.event_engine) # Set layout form = QtWidgets.QFormLayout() form.addRow("标准户接口", self.source_combo) form.addRow("跟单户接口", self.target_combo) form.addRow("跟单方向", self.follow_direction_combo) form.addRow("超时禁跟(秒)", self.follow_timeout_line) form.addRow("跟随倍数", self.multiples_line) form.addRow("是否日内交易", self.intraday_trading_combo) form.addRow(self.start_button) form.addRow(self.stop_button) form_action = QtWidgets.QFormLayout() form_action.addRow("日内模式品种", self.intraday_combo) form_action.addRow("禁止同步合约", self.skip_contracts_combo) form_action.addRow("跟单委托手数", self.order_vol_combo) form_action.addRow(self.modify_pos_button) form_action.addRow(self.set_skip_button) form_action.addRow(self.set_order_button) form_action.addRow(self.sync_pos_button) form_action.addRow(self.close_hedged_pos_button) vbox = QtWidgets.QVBoxLayout() vbox.addLayout(form) vbox.addStretch() vbox.addLayout(form_action) vbox.addStretch() grid = QtWidgets.QGridLayout() grid.addLayout(vbox, 0, 0, 2, 1) grid.addWidget(self.pos_delta_monitor, 0, 1) grid.addWidget(self.log_monitor, 1, 1) grid.setColumnStretch(0, 1) grid.setColumnStretch(1, 3) self.setLayout(grid) def register_event(self): """""" # self.timer.start(3000) # self.timer.timeout.connect(self.refresh_symbol_list) # self.timer.timeout.connect(self.test_timer2) # self.signal_log.connect(self.process_log_event) # self.event_engine.register(EVENT_FOLLOW_LOG, self.signal_log.emit) pass def set_sync_symbol(self, vt_symbol: str): """ Set symbol to be synced """ self.sync_symbol = vt_symbol self.write_log(f"选中合约{self.sync_symbol}") def set_follow_direction(self, follow_direction: str): """""" if follow_direction == "正向跟随": self.follow_engine.set_parameters('inverse_follow', False) else: self.follow_engine.set_parameters('inverse_follow', True) self.write_log(f"是否反向跟单:{self.follow_engine.inverse_follow} 设置成功") def set_is_intraday_trading(self, intraday_flag: str): """""" if intraday_flag == "是": self.follow_engine.set_parameters('is_intraday_trading', True) else: self.follow_engine.set_parameters('is_intraday_trading', False) self.write_log(f"是否日内交易:{self.follow_engine.is_intraday_trading}") def get_current_follow_direction(self): """""" inverse_follow = self.follow_engine.inverse_follow if not inverse_follow: self.follow_direction_combo.setCurrentIndex(0) else: self.follow_direction_combo.setCurrentIndex(1) def get_current_intraday_trading(self): """""" is_intraday_trading = self.follow_engine.is_intraday_trading if is_intraday_trading: self.intraday_trading_combo.setCurrentIndex(0) else: self.intraday_trading_combo.setCurrentIndex(1) def set_follow_timeout(self): text = self.follow_timeout_line.text() self.follow_engine.set_parameters('filter_trade_timeout', int(text)) self.write_log(f"成交单超时:{self.follow_engine.filter_trade_timeout} 秒设置成功") def set_multiples(self): """""" text = self.multiples_line.text() self.follow_engine.set_parameters('multiples', int(text)) self.write_log(f"跟随倍数:{self.follow_engine.multiples} 设置成功") def refresh_gateway_name(self): """""" gateways = self.follow_engine.get_connected_gateway_names() if not gateways: self.write_log(f"获取不到可用接口名称,请先连接接口") else: for combo in [self.source_combo, self.target_combo]: combo.clear() combo.addItems(gateways) self.write_log(f"接口名称获取成功") def refresh_skip_contracts(self): """""" self.skip_contracts_combo.clear() symbol_list = self.follow_engine.get_skip_contracts() self.skip_contracts_combo.addItems(symbol_list) def refresh_intraday(self): """""" self.intraday_combo.clear() symbol_list = self.follow_engine.get_intraday_symbols() self.intraday_combo.addItems(symbol_list) def refresh_order_vols(self): self.order_vol_combo.clear() vol_list = self.follow_engine.get_order_vols_to_follow() vol_str_list = [str(vol) for vol in vol_list] self.order_vol_combo.addItems(vol_str_list) def test_timer(self): """""" self.write_log("定时器测试") def test_timer2(self): """""" self.write_log("定时器多槽测试") def start_follow(self): """""" if not self.is_gateway_inited: self.write_log("标准户接口和跟单户接口未全部初始化,请检查RPC是否已连接服务器,然后重启程序重试。") self.start_button.setEnabled(False) return self.pos_delta_monitor.resize_columns() source = self.source_combo.currentText() target = self.target_combo.currentText() if source == target: self.follow_engine.write_log("标准户接口和跟单户接口不能是同一个") return self.follow_engine.set_gateways(source, target) result = self.follow_engine.start() if result: self.start_button.setEnabled(False) self.stop_button.setEnabled(True) self.sync_pos_button.setEnabled(True) self.close_hedged_pos_button.setEnabled(True) # self.modify_pos_button.setEnabled(False) self.set_skip_button.setEnabled(True) self.source_combo.setEnabled(False) self.target_combo.setEnabled(False) self.follow_direction_combo.setEnabled(False) def stop_follow(self): """""" result = self.follow_engine.stop() if result: self.start_button.setEnabled(True) self.stop_button.setEnabled(False) self.sync_pos_button.setEnabled(False) self.close_hedged_pos_button.setEnabled(False) # self.modify_pos_button.setEnabled(True) def validate_vt_symbol(self, vt_symbol: str): """""" if not vt_symbol: self.write_log(f"合约名称不能为空,请正确选择或输入") return vt_symbol = vt_symbol.strip() contract = self.main_engine.get_contract(vt_symbol) if not contract: self.write_log(f"{vt_symbol}无法匹配接口的可交易的合约,请检查合约是否正确或接口是否连接") else: return vt_symbol def sync_pos(self): dialog = SyncPosEditor(self, self.follow_engine) dialog.exec_() def manual_modify_pos(self): dialog = PosEditor(self, self.follow_engine) dialog.exec_() def set_skip_contracts(self): dialog = SkipContractEditor(self, self.follow_engine) dialog.exec_() def set_order_setting(self): dialog = OrderSettingEditor(self, self.follow_engine) dialog.exec_() def close_hedged_pos(self): dialog = CloseHedgedDialog(self, self.follow_engine) dialog.exec_() def write_log(self, msg: str): """""" self.follow_engine.write_log(msg) def clear_log(self): """""" self.log_monitor.setRowCount(0) def show(self): """""" self.showNormal()
class BacktesterManager(QtWidgets.QWidget): """""" 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_strategy_settings() self.init_ui() self.register_event() self.backtester_engine.init_engine() 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 def init_ui(self): """""" self.setWindowTitle("CTA回测") # Setting Part self.class_combo = QtWidgets.QComboBox() self.class_combo.addItems(self.class_names) self.symbol_line = QtWidgets.QLineEdit("IF88.CFFEX") self.interval_combo = QtWidgets.QComboBox() for inteval in Interval: self.interval_combo.addItem(inteval.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") 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) for button in [ backtesting_button, optimization_button, downloading_button, self.result_button, self.order_button, self.trade_button, self.daily_button, self.candle_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) 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.addWidget(self.candle_button) left_vbox.addStretch() left_vbox.addWidget(optimization_button) left_vbox.addWidget(self.result_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 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) 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.date().toPyDate() end = self.end_date_edit.date().toPyDate() 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()) 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, 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.date().toPyDate() end = self.end_date_edit.date().toPyDate() 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()) 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, 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()) end = datetime(end_date.year(), end_date.month(), end_date.day()) 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 show(self): """""" self.showMaximized()
class RadarManager(QtWidgets.QWidget): """""" signal_log = QtCore.pyqtSignal(Event) def __init__(self, main_engine: MainEngine, event_engine: EventEngine): """""" super().__init__() self.main_engine: MainEngine = main_engine self.event_engine: EventEngine = event_engine self.radar_engine: RadarEngine = main_engine.get_engine(APP_NAME) self.init_ui() self.register_event() self.radar_engine.init() def init_ui(self) -> None: """""" self.setWindowTitle("市场雷达") self.radar_monitor = RadarMonitor(self.radar_engine) self.signal_monitor = SignalMonitor(self.radar_engine) self.log_monitor = QtWidgets.QTextEdit() self.log_monitor.setReadOnly(True) self.name_line = QtWidgets.QLineEdit() self.formula_line = QtWidgets.QLineEdit() self.a_line = QtWidgets.QLineEdit() self.b_line = QtWidgets.QLineEdit() self.c_line = QtWidgets.QLineEdit() self.d_line = QtWidgets.QLineEdit() self.e_line = QtWidgets.QLineEdit() self.ndigits_spin = QtWidgets.QSpinBox() self.ndigits_spin.setMinimum(0) self.ndigits_spin.setValue(2) add_button = QtWidgets.QPushButton("添加") add_button.clicked.connect(self.add_rule) add_button.setFixedHeight(add_button.sizeHint().height() * 2) edit_button = QtWidgets.QPushButton("修改") edit_button.clicked.connect(self.edit_rule) edit_button.setFixedHeight(edit_button.sizeHint().height() * 2) load_button = QtWidgets.QPushButton("导入CSV") load_button.clicked.connect(self.load_csv) form = QtWidgets.QFormLayout() form.addRow("名称", self.name_line) form.addRow("公式", self.formula_line) form.addRow("A", self.a_line) form.addRow("B", self.b_line) form.addRow("C", self.c_line) form.addRow("D", self.d_line) form.addRow("E", self.e_line) form.addRow("小数", self.ndigits_spin) form.addRow(add_button) form.addRow(edit_button) vbox = QtWidgets.QVBoxLayout() vbox.addLayout(form) vbox.addStretch() vbox.addWidget(load_button) left_widget = QtWidgets.QWidget() left_widget.setLayout(vbox) left_widget.setFixedWidth(300) hbox = QtWidgets.QHBoxLayout() hbox.addWidget(left_widget) hbox.addWidget(self.signal_monitor) hbox.addWidget(self.log_monitor) vbox2 = QtWidgets.QVBoxLayout() vbox2.addWidget(self.radar_monitor) vbox2.addLayout(hbox) self.setLayout(vbox2) def register_event(self) -> None: """""" self.signal_log.connect(self.process_log_event) self.event_engine.register(EVENT_RADAR_LOG, self.signal_log.emit) def process_log_event(self, event: Event) -> None: """""" log = event.data time_str = log.time.strftime("%H:%M:%S") msg = f"{time_str}\t{log.msg}" self.log_monitor.append(msg) def add_rule(self) -> None: """""" name, formula, params, ndigits = self.get_rule_setting() self.radar_engine.add_rule(name, formula, params, ndigits) self.radar_engine.save_setting() def edit_rule(self) -> None: """""" name, formula, params, ndigits = self.get_rule_setting() self.radar_engine.edit_rule(name, formula, params, ndigits) self.radar_engine.save_setting() def get_rule_setting(self) -> tuple: """""" name = self.name_line.text() formula = self.formula_line.text() a = self.a_line.text() b = self.b_line.text() c = self.c_line.text() d = self.d_line.text() e = self.e_line.text() params = {} if a: params["A"] = a if b: params["B"] = b if c: params["C"] = c if d: params["D"] = d if e: params["E"] = e ndigits = self.ndigits_spin.value() return name, formula, params, ndigits def show(self): """""" self.showMaximized() def load_csv(self): """""" path, type_ = QtWidgets.QFileDialog.getOpenFileName( self, u"导入CSV配置", "", "CSV(*.csv)") if not path: return # Create csv DictReader with open(path, "r") as f: reader = DictReader(f) for row in reader: name = row["名称"] formula = row["公式"] ndigits = int(row["小数"]) params = {} for param in ["A", "B", "C", "D", "E"]: vt_symbol = row.get(param, "") if vt_symbol: params[param] = vt_symbol self.radar_engine.add_rule(name, formula, params, ndigits) self.radar_engine.save_setting()
class OptionManualTrader(QtWidgets.QWidget): """""" signal_tick = QtCore.pyqtSignal(TickData) def __init__(self, option_engine: OptionEngine, portfolio_name: str): """""" super().__init__() self.option_engine = option_engine self.main_engine: MainEngine = option_engine.main_engine self.event_engine: EventEngine = option_engine.event_engine self.contracts: Dict[str, ContractData] = {} self.vt_symbol = "" self.init_ui() self.init_contracts() def init_ui(self) -> None: """""" self.setWindowTitle("期权交易") # Trading Area self.symbol_line = QtWidgets.QLineEdit() self.symbol_line.returnPressed.connect(self._update_symbol) float_validator = QtGui.QDoubleValidator() float_validator.setBottom(0) self.price_line = QtWidgets.QLineEdit() self.price_line.setValidator(float_validator) int_validator = QtGui.QIntValidator() int_validator.setBottom(0) self.volume_line = QtWidgets.QLineEdit() self.volume_line.setValidator(int_validator) 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.OPEN.value, Offset.CLOSE.value]) order_button = QtWidgets.QPushButton("委托") order_button.clicked.connect(self.send_order) cancel_button = QtWidgets.QPushButton("全撤") cancel_button.clicked.connect(self.cancel_all) form1 = QtWidgets.QFormLayout() form1.addRow("代码", self.symbol_line) form1.addRow("方向", self.direction_combo) form1.addRow("开平", self.offset_combo) form1.addRow("价格", self.price_line) form1.addRow("数量", self.volume_line) form1.addRow(order_button) form1.addRow(cancel_button) # Depth Area bid_color = "rgb(255,174,201)" ask_color = "rgb(160,255,160)" self.bp1_label = self.create_label(bid_color) self.bp2_label = self.create_label(bid_color) self.bp3_label = self.create_label(bid_color) self.bp4_label = self.create_label(bid_color) self.bp5_label = self.create_label(bid_color) self.bv1_label = self.create_label(bid_color, alignment=QtCore.Qt.AlignRight) self.bv2_label = self.create_label(bid_color, alignment=QtCore.Qt.AlignRight) self.bv3_label = self.create_label(bid_color, alignment=QtCore.Qt.AlignRight) self.bv4_label = self.create_label(bid_color, alignment=QtCore.Qt.AlignRight) self.bv5_label = self.create_label(bid_color, alignment=QtCore.Qt.AlignRight) self.ap1_label = self.create_label(ask_color) self.ap2_label = self.create_label(ask_color) self.ap3_label = self.create_label(ask_color) self.ap4_label = self.create_label(ask_color) self.ap5_label = self.create_label(ask_color) self.av1_label = self.create_label(ask_color, alignment=QtCore.Qt.AlignRight) self.av2_label = self.create_label(ask_color, alignment=QtCore.Qt.AlignRight) self.av3_label = self.create_label(ask_color, alignment=QtCore.Qt.AlignRight) self.av4_label = self.create_label(ask_color, alignment=QtCore.Qt.AlignRight) self.av5_label = self.create_label(ask_color, alignment=QtCore.Qt.AlignRight) self.lp_label = self.create_label() self.return_label = self.create_label(alignment=QtCore.Qt.AlignRight) min_width = 70 self.lp_label.setMinimumWidth(min_width) self.return_label.setMinimumWidth(min_width) form2 = QtWidgets.QFormLayout() form2.addRow(self.ap5_label, self.av5_label) form2.addRow(self.ap4_label, self.av4_label) form2.addRow(self.ap3_label, self.av3_label) form2.addRow(self.ap2_label, self.av2_label) form2.addRow(self.ap1_label, self.av1_label) form2.addRow(self.lp_label, self.return_label) form2.addRow(self.bp1_label, self.bv1_label) form2.addRow(self.bp2_label, self.bv2_label) form2.addRow(self.bp3_label, self.bv3_label) form2.addRow(self.bp4_label, self.bv4_label) form2.addRow(self.bp5_label, self.bv5_label) # Set layout hbox = QtWidgets.QHBoxLayout() hbox.addLayout(form1) hbox.addLayout(form2) self.setLayout(hbox) def init_contracts(self) -> None: """""" contracts = self.main_engine.get_all_contracts() for contract in contracts: self.contracts[contract.symbol] = contract def connect_signal(self) -> None: """""" self.signal_tick.connect(self.update_tick) def send_order(self) -> None: """""" symbol = self.symbol_line.text() contract = self.contracts.get(symbol, None) if not contract: return 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 = int(volume_text) direction = Direction(self.direction_combo.currentText()) offset = Offset(self.offset_combo.currentText()) req = OrderRequest(symbol=contract.symbol, exchange=contract.exchange, direction=direction, type=OrderType.LIMIT, offset=offset, volume=volume, price=price) self.main_engine.send_order(req, contract.gateway_name) def cancel_all(self) -> None: """""" for order in self.main_engine.get_all_active_orders(): req = order.create_cancel_request() self.main_engine.cancel_order(req, order.gateway_name) def update_symbol(self, cell: MonitorCell) -> None: """""" if not cell.vt_symbol: return symbol = cell.vt_symbol.split(".")[0] self.symbol_line.setText(symbol) self._update_symbol() def _update_symbol(self) -> None: """""" symbol = self.symbol_line.text() contract = self.contracts.get(symbol, None) if contract and contract.vt_symbol == self.vt_symbol: return if self.vt_symbol: self.event_engine.unregister(EVENT_TICK + self.vt_symbol, self.process_tick_event) self.clear_data() self.vt_symbol = "" if not contract: return vt_symbol = contract.vt_symbol self.vt_symbol = vt_symbol tick = self.main_engine.get_tick(vt_symbol) if tick: self.update_tick(tick) self.event_engine.unregister(EVENT_TICK + vt_symbol, self.process_tick_event) def create_label(self, color: str = "", alignment: int = QtCore.Qt.AlignLeft) -> QtWidgets.QLabel: """ Create label with certain font color. """ label = QtWidgets.QLabel("-") if color: label.setStyleSheet(f"color:{color}") label.setAlignment(alignment) return label def process_tick_event(self, event: Event) -> None: """""" tick = event.data if tick.vt_symbol != self.vt_symbol: return self.signal_tick.emit(tick) def update_tick(self, tick: TickData) -> None: """""" self.lp_label.setText(str(tick.last_price)) self.bp1_label.setText(str(tick.bid_price_1)) self.bv1_label.setText(str(tick.bid_volume_1)) self.ap1_label.setText(str(tick.ask_price_1)) self.av1_label.setText(str(tick.ask_volume_1)) if tick.pre_close: r = (tick.last_price / tick.pre_close - 1) * 100 self.return_label.setText(f"{r:.2f}%") if tick.bid_price_2: self.bp2_label.setText(str(tick.bid_price_2)) self.bv2_label.setText(str(tick.bid_volume_2)) self.ap2_label.setText(str(tick.ask_price_2)) self.av2_label.setText(str(tick.ask_volume_2)) self.bp3_label.setText(str(tick.bid_price_3)) self.bv3_label.setText(str(tick.bid_volume_3)) self.ap3_label.setText(str(tick.ask_price_3)) self.av3_label.setText(str(tick.ask_volume_3)) self.bp4_label.setText(str(tick.bid_price_4)) self.bv4_label.setText(str(tick.bid_volume_4)) self.ap4_label.setText(str(tick.ask_price_4)) self.av4_label.setText(str(tick.ask_volume_4)) self.bp5_label.setText(str(tick.bid_price_5)) self.bv5_label.setText(str(tick.bid_volume_5)) self.ap5_label.setText(str(tick.ask_price_5)) self.av5_label.setText(str(tick.ask_volume_5)) def clear_data(self) -> None: """""" self.lp_label.setText("-") self.return_label.setText("-") self.bp1_label.setText("-") self.bv1_label.setText("-") self.ap1_label.setText("-") self.av1_label.setText("-") self.bp2_label.setText("-") self.bv2_label.setText("-") self.ap2_label.setText("-") self.av2_label.setText("-") self.bp3_label.setText("-") self.bv3_label.setText("-") self.ap3_label.setText("-") self.av3_label.setText("-") self.bp4_label.setText("-") self.bv4_label.setText("-") self.ap4_label.setText("-") self.av4_label.setText("-") self.bp5_label.setText("-") self.bv5_label.setText("-") self.ap5_label.setText("-") self.av5_label.setText("-")
class RadarMonitor(QtWidgets.QTableWidget): """""" signal_rule = QtCore.pyqtSignal(Event) signal_update = QtCore.pyqtSignal(Event) def __init__(self, radar_engine: RadarEngine): """""" super().__init__() self.radar_engine: RadarEngine = radar_engine self.event_engine: EventEngine = radar_engine.event_engine self.cells: Dict[str, Dict[str, RadarCell]] = {} self.init_ui() self.register_event() def init_ui(self) -> None: """""" headers = ["名称", "数值", "时间", "公式", "A", "B", "C", "D", "E", "小数", " "] self.setColumnCount(len(headers)) self.setHorizontalHeaderLabels(headers) self.verticalHeader().setVisible(False) self.setEditTriggers(self.NoEditTriggers) self.setAlternatingRowColors(True) h_header = self.horizontalHeader() h_header.setSectionResizeMode(h_header.Stretch) def register_event(self) -> None: """""" self.signal_rule.connect(self.process_rule_event) self.signal_update.connect(self.process_update_event) self.event_engine.register(EVENT_RADAR_RULE, self.signal_rule.emit) self.event_engine.register(EVENT_RADAR_UPDATE, self.signal_update.emit) def process_rule_event(self, event: Event) -> None: """""" rule_data = event.data name = rule_data["name"] formula = rule_data["formula"] params = rule_data["params"] ndigits = rule_data["ndigits"] if name not in self.cells: name_button = QtWidgets.QPushButton(name) name_func = partial(self.add_signal, name) name_button.clicked.connect(name_func) name_button.setToolTip("添加雷达信号") value_cell = RadarCell() time_cell = RadarCell() formula_cell = RadarCell(formula) a_cell = RadarCell(params.get("A", "")) b_cell = RadarCell(params.get("B", "")) c_cell = RadarCell(params.get("C", "")) d_cell = RadarCell(params.get("D", "")) e_cell = RadarCell(params.get("E", "")) ndigits_cell = RadarCell(str(ndigits)) remove_func = partial(self.remove_rule, name) remove_button = QtWidgets.QPushButton("删除") remove_button.clicked.connect(remove_func) self.insertRow(0) self.setCellWidget(0, 0, name_button) self.setItem(0, 1, value_cell) self.setItem(0, 2, time_cell) self.setItem(0, 3, formula_cell) self.setItem(0, 4, a_cell) self.setItem(0, 5, b_cell) self.setItem(0, 6, c_cell) self.setItem(0, 7, d_cell) self.setItem(0, 8, e_cell) self.setItem(0, 9, ndigits_cell) self.setCellWidget(0, 10, remove_button) self.cells[name] = { "name": name_button, "value": value_cell, "time": time_cell, "formula": formula_cell, "a": a_cell, "b": b_cell, "c": c_cell, "d": d_cell, "e": e_cell, "ndigits": ndigits_cell } else: row_cells = self.cells[name] row_cells["formula"].setText(formula) row_cells["a"].setText(params.get("A", "")) row_cells["b"].setText(params.get("B", "")) row_cells["c"].setText(params.get("C", "")) row_cells["d"].setText(params.get("D", "")) row_cells["e"].setText(params.get("E", "")) row_cells["ndigits"].setText(str(ndigits)) def process_update_event(self, event: Event) -> None: """""" radar_data = event.data row_cells = self.cells.get(radar_data["name"], None) if row_cells: row_cells["value"].setText(str(radar_data["value"])) row_cells["time"].setText(str(radar_data["time"])) def remove_rule(self, name: str) -> None: """""" rule_names = list(self.cells.keys()) rule_names.reverse() row = rule_names.index(name) self.cells.pop(name) self.removeRow(row) self.radar_engine.remove_rule(name) self.radar_engine.save_setting() def add_signal(self, name: str) -> None: """""" dialog = SignalDialog(name, self.radar_engine) dialog.exec()