Esempio n. 1
0
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()
Esempio n. 2
0
class SpreadStrategyMonitor(QtWidgets.QWidget):
    """"""

    signal_strategy = QtCore.pyqtSignal(Event)

    def __init__(self, spread_engine: SpreadEngine):
        super().__init__()

        self.strategy_engine = spread_engine.strategy_engine
        self.main_engine = spread_engine.main_engine
        self.event_engine = spread_engine.event_engine

        self.managers = {}

        self.init_ui()
        self.register_event()

    def init_ui(self):
        """"""
        self.scroll_layout = QtWidgets.QVBoxLayout()
        self.scroll_layout.addStretch()

        scroll_widget = QtWidgets.QWidget()
        scroll_widget.setLayout(self.scroll_layout)

        scroll_area = QtWidgets.QScrollArea()
        scroll_area.setWidgetResizable(True)
        scroll_area.setWidget(scroll_widget)

        vbox = QtWidgets.QVBoxLayout()
        vbox.addWidget(scroll_area)
        self.setLayout(vbox)

    def register_event(self):
        """"""
        self.signal_strategy.connect(self.process_strategy_event)

        self.event_engine.register(
            EVENT_SPREAD_STRATEGY, self.signal_strategy.emit
        )

    def process_strategy_event(self, event):
        """
        Update strategy status onto its monitor.
        """
        data = event.data
        strategy_name = data["strategy_name"]

        if strategy_name in self.managers:
            manager = self.managers[strategy_name]
            manager.update_data(data)
        else:
            manager = SpreadStrategyWidget(self, self.strategy_engine, data)
            self.scroll_layout.insertWidget(0, manager)
            self.managers[strategy_name] = manager

    def remove_strategy(self, strategy_name):
        """"""
        manager = self.managers.pop(strategy_name)
        manager.deleteLater()
Esempio n. 3
0
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)
Esempio n. 4
0
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)
Esempio n. 5
0
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)
Esempio n. 6
0
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()
Esempio n. 7
0
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()
Esempio n. 8
0
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()
Esempio n. 9
0
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()
Esempio n. 10
0
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
Esempio n. 11
0
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}")
Esempio n. 12
0
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()
Esempio n. 13
0
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)
Esempio n. 14
0
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)
Esempio n. 15
0
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)
Esempio n. 16
0
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)
Esempio n. 17
0
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()
Esempio n. 18
0
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}")
Esempio n. 19
0
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)
Esempio n. 20
0
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))
Esempio n. 21
0
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)
Esempio n. 22
0
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)
Esempio n. 23
0
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)
Esempio n. 24
0
class ComboBox(QtWidgets.QComboBox):
    pop_show = QtCore.pyqtSignal()

    def showPopup(self):
        self.pop_show.emit()
        super(ComboBox, self).showPopup()
Esempio n. 25
0
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)
Esempio n. 26
0
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()
Esempio n. 27
0
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()
Esempio n. 28
0
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()
Esempio n. 29
0
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("-")
Esempio n. 30
0
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()