diff --git a/tests/trader/run.py b/tests/trader/run.py index b06b4af4..cf698f0e 100644 --- a/tests/trader/run.py +++ b/tests/trader/run.py @@ -25,6 +25,7 @@ from vnpy.app.csv_loader import CsvLoaderApp from vnpy.app.algo_trading import AlgoTradingApp from vnpy.app.cta_backtester import CtaBacktesterApp from vnpy.app.data_recorder import DataRecorderApp +from vnpy.app.risk_manager import RiskManagerApp def main(): @@ -55,6 +56,7 @@ def main(): main_engine.add_app(CsvLoaderApp) main_engine.add_app(AlgoTradingApp) main_engine.add_app(DataRecorderApp) + main_engine.add_app(RiskManagerApp) main_window = MainWindow(main_engine, event_engine) main_window.showMaximized() diff --git a/vnpy/app/risk_manager/__init__.py b/vnpy/app/risk_manager/__init__.py index 3c51a1e7..0b867f06 100644 --- a/vnpy/app/risk_manager/__init__.py +++ b/vnpy/app/risk_manager/__init__.py @@ -8,7 +8,7 @@ class RiskManagerApp(BaseApp): app_name = APP_NAME app_module = __module__ app_path = Path(__file__).parent - display_name = "风险控制" + display_name = "交易风控" engine_class = RiskManagerEngine widget_name = "RiskManager" - icon_name = "risk_manager.ico" + icon_name = "rm.ico" diff --git a/vnpy/app/risk_manager/engine.py b/vnpy/app/risk_manager/engine.py index 84fa1c9c..35ab4a74 100644 --- a/vnpy/app/risk_manager/engine.py +++ b/vnpy/app/risk_manager/engine.py @@ -15,32 +15,42 @@ APP_NAME = "RiskManager" class RiskManagerEngine(BaseEngine): """""" setting_filename = "risk_manager_setting.json" - + def __init__(self, main_engine: MainEngine, event_engine: EventEngine): """""" super().__init__(main_engine, event_engine, APP_NAME) - + self.main_engine = main_engine self.event_engine = event_engine - + self.active = False - self.order_flow_count = 0 - self.order_flow_limit = 50 - self.order_flow_clear = 1 - self.order_flow_timer = 0 - self.order_size_limit = 100 - self.trade_count = 0 - self.trade_limit = 1000 - self.order_cancel_limit = 10 - self.order_cancel_counts = defaultdict(int) - self.active_order_limit = 20 - - # Patch send order function of MainEngine - self._send_order = self.main_engine.send_order - self.main_engine.send_order = self.send_order + + self.order_flow_count = 0 + self.order_flow_limit = 50 + + self.order_flow_clear = 1 + self.order_flow_timer = 0 + + self.order_size_limit = 100 + + self.trade_count = 0 + self.trade_limit = 1000 + + self.order_cancel_limit = 500 + self.order_cancel_counts = defaultdict(int) + + self.active_order_limit = 50 self.load_setting() self.register_event() + self.patch_send_order() + + def patch_send_order(self): + """ + Patch send order function of MainEngine. + """ + self._send_order = self.main_engine.send_order + self.main_engine.send_order = self.send_order def send_order(self, req: OrderRequest, gateway_name: str): """""" @@ -49,20 +59,23 @@ class RiskManagerEngine(BaseEngine): return "" return self._send_order(req, gateway_name) - - def load_setting(self): - """""" - setting = load_json(self.setting_filename) - self.active = setting.get("active", self.active) - self.order_flow_limit = setting.get("order_flow_limit", self.order_flow_count) - self.order_flow_clear = setting.get("order_flow_clear", self.order_flow_clear) - self.order_size_limit = setting.get("order_size_limit", self.order_size_limit) - self.trade_limit = setting.get("trade_limit", self.trade_limit) - self.active_order_limit = setting.get("active_order_limit", self.active_order_limit) - self.order_cancel_limit = setting.get("order_cancel_limit", self.order_cancel_limit) - - def save_setting(self): + def update_setting(self, setting: dict): + """""" + self.active = setting["active"] + self.order_flow_limit = setting["order_flow_limit"] + self.order_flow_clear = setting["order_flow_clear"] + self.order_size_limit = setting["order_size_limit"] + self.trade_limit = setting["trade_limit"] + self.active_order_limit = setting["active_order_limit"] + self.order_cancel_limit = setting["order_cancel_limit"] + + if self.active: + self.write_log("交易风控功能启动") + else: + self.write_log("交易风控功能停止") + + def get_setting(self): """""" setting = { "active": self.active, @@ -73,27 +86,39 @@ class RiskManagerEngine(BaseEngine): "active_order_limit": self.active_order_limit, "order_cancel_limit": self.order_cancel_limit, } + return setting + def load_setting(self): + """""" + setting = load_json(self.setting_filename) + if not setting: + return + + self.update_setting(setting) + + def save_setting(self): + """""" + setting = self.get_setting() save_json(self.setting_filename, setting) - + def register_event(self): """""" self.event_engine.register(EVENT_TRADE, self.process_trade_event) self.event_engine.register(EVENT_TIMER, self.process_timer_event) self.event_engine.register(EVENT_ORDER, self.process_order_event) - + def process_order_event(self, event: Event): """""" order = event.data if order.status != Status.CANCELLED: - return + return self.order_cancel_counts[order.symbol] += 1 - + def process_trade_event(self, event: Event): """""" trade = event.data self.trade_count += trade.volume - + def process_timer_event(self, event: Event): """""" self.order_flow_timer += 1 @@ -101,9 +126,9 @@ class RiskManagerEngine(BaseEngine): if self.order_flow_timer >= self.order_flow_clear: self.order_flow_count = 0 self.order_flow_timer = 0 - - def write_risk_log(self, msg: str): - """""" + + def write_log(self, msg: str): + """""" log = LogData(msg=msg, gateway_name="RiskManager") event = Event(type=EVENT_LOG, data=log) self.event_engine.put(event) @@ -115,81 +140,39 @@ class RiskManagerEngine(BaseEngine): # Check order volume if req.volume <= 0: - self.write_risk_log("委托数量必须大于0") + self.write_log("委托数量必须大于0") return False - + if req.volume > self.order_size_limit: - self.write_risk_log(f"单笔委托数量{req.volume},超过限制{self.order_size_limit}") + self.write_log( + f"单笔委托数量{req.volume},超过限制{self.order_size_limit}") return False # Check trade volume if self.trade_count >= self.trade_limit: - self.write_risk_log(f"今日总成交合约数量{self.trade_count},超过限制{self.trade_limit}") + self.write_log( + f"今日总成交合约数量{self.trade_count},超过限制{self.trade_limit}") return False # Check flow count if self.order_flow_count >= self.order_flow_limit: - self.write_risk_log(f"委托流数量{self.order_flow_count},超过限制每{self.order_flow_clear}秒{self.order_flow_limit}次") + self.write_log( + f"委托流数量{self.order_flow_count},超过限制每{self.order_flow_clear}秒{self.order_flow_limit}次") return False # Check all active orders active_order_count = len(self.main_engine.get_all_active_orders()) if active_order_count >= self.active_order_limit: - self.write_risk_log(f"当前活动委托次数{active_order_count},超过限制{self.active_order_limit}") + self.write_log( + f"当前活动委托次数{active_order_count},超过限制{self.active_order_limit}") return False # Check order cancel counts - if req.symbol in self.order_cancel_counts and self.order_cancel_counts[req.symbol] >= self.order_cancel_limit: - self.write_risk_log(f"当日{req.symbol}撤单次数{self.order_cancel_counts[req.symbol]},超过限制{self.order_cancel_limit}") + if req.symbol in self.order_cancel_counts and self.order_cancel_counts[req.symbol] >= self.order_cancel_limit: + self.write_log( + f"当日{req.symbol}撤单次数{self.order_cancel_counts[req.symbol]},超过限制{self.order_cancel_limit}") return False # Add flow count if pass all checks self.order_flow_count += 1 return True - - def clear_order_flow_count(self): - """""" - self.order_flow_count = 0 - self.write_risk_log("清空流控计数") - - def clear_trade_count(self): - """""" - self.trade_count = 0 - self.write_risk_log("清空总成交计数") - - def set_order_flow_limit(self, n): - """""" - self.order_flow_limit = n - - def set_order_flow_clear(self, n): - """""" - self.order_flow_clear = n - - def set_order_size_limit(self, n): - """""" - self.order_size_limit = n - - def set_trade_limit(self, n): - """""" - self.trade_limit = n - - def set_active_order_limit(self, n): - """""" - self.active_order_limit = n - - def set_order_cancel_limit(self, n): - """""" - self.order_cancel_limit = n - - def switch_engine_status(self): - """""" - self.active = not self.active - - if self.active: - self.write_risk_log("风险管理功能启动") - else: - self.write_risk_log("风险管理功能停止") - - def stop(self): - """""" - self.save_setting() diff --git a/vnpy/app/risk_manager/ui/risk_manager.ico b/vnpy/app/risk_manager/ui/risk_manager.ico deleted file mode 100644 index 4b376934..00000000 Binary files a/vnpy/app/risk_manager/ui/risk_manager.ico and /dev/null differ diff --git a/vnpy/app/risk_manager/ui/rm.ico b/vnpy/app/risk_manager/ui/rm.ico new file mode 100644 index 00000000..751b8255 Binary files /dev/null and b/vnpy/app/risk_manager/ui/rm.ico differ diff --git a/vnpy/app/risk_manager/ui/widget.py b/vnpy/app/risk_manager/ui/widget.py index d4baaa18..c9ce1e68 100644 --- a/vnpy/app/risk_manager/ui/widget.py +++ b/vnpy/app/risk_manager/ui/widget.py @@ -4,7 +4,7 @@ from vnpy.trader.ui import QtWidgets from ..engine import APP_NAME -class RiskManager(QtWidgets.QWidget): +class RiskManager(QtWidgets.QDialog): """""" def __init__(self, main_engine: MainEngine, event_engine: EventEngine): @@ -13,104 +13,95 @@ class RiskManager(QtWidgets.QWidget): self.main_engine = main_engine self.event_engine = event_engine - self.risk_manager_engine = main_engine.get_engine(APP_NAME) + self.rm_engine = main_engine.get_engine(APP_NAME) self.init_ui() - self.update_engine_status() def init_ui(self): """""" - self.setWindowTitle("风险控制") + self.setWindowTitle("交易风控") - # SpinBox - self.order_flow_limit = RiskManagerSpinBox(0) - self.order_flow_clear = RiskManagerSpinBox(0) - self.order_size_limit = RiskManagerSpinBox(0) - self.trade_limit = RiskManagerSpinBox(0) - self.active_order_limit = RiskManagerSpinBox(0) - self.order_cancel_limit = RiskManagerSpinBox(0) + # Create widgets + self.active_combo = QtWidgets.QComboBox() + self.active_combo.addItems(["停止", "启动"]) - # Button - self.switch_button = QtWidgets.QPushButton("风控模块未启动") - clear_order_count_button = QtWidgets.QPushButton("清空流控计数") - clear_trade_count_button = QtWidgets.QPushButton("清空总成交计数") - save_setting_button = QtWidgets.QPushButton("保存设置") + self.flow_limit_spin = RiskManagerSpinBox() + self.flow_clear_spin = RiskManagerSpinBox() + self.size_limit_spin = RiskManagerSpinBox() + self.trade_limit_spin = RiskManagerSpinBox() + self.active_limit_spin = RiskManagerSpinBox() + self.cancel_limit_spin = RiskManagerSpinBox() - # Grid layout - Label = QtWidgets.QLabel - grid = QtWidgets.QGridLayout() - grid.addWidget(Label("工作状态"), 0, 0) - grid.addWidget(self.switch_button, 0, 1) - grid.addWidget(Label("流控上限"), 1, 0) - grid.addWidget(self.order_flow_limit, 1, 1) - grid.addWidget(Label("流控清空(秒)"), 2, 0) - grid.addWidget(self.order_flow_clear, 2, 1) - grid.addWidget(Label("单笔委托上限"), 3, 0) - grid.addWidget(self.order_size_limit, 3, 1) - grid.addWidget(Label("总成交上限"), 4, 0) - grid.addWidget(self.trade_limit, 4, 1) - grid.addWidget(Label("活动订单上限"), 5, 0) - grid.addWidget(self.active_order_limit, 5, 1) - grid.addWidget(Label("单合约撤单上限"), 6, 0) - grid.addWidget(self.order_cancel_limit, 6, 1) + save_button = QtWidgets.QPushButton("保存") + save_button.clicked.connect(self.save_setting) - # Horizontal box layout - hbox = QtWidgets.QHBoxLayout() - hbox.addWidget(clear_order_count_button) - hbox.addWidget(clear_trade_count_button) - hbox.addWidget(save_setting_button) + # Form layout + form = QtWidgets.QFormLayout() + form.addRow("风控运行状态", self.active_combo) + form.addRow("委托流控上限(笔)", self.flow_limit_spin) + form.addRow("委托流控清空(秒)", self.flow_clear_spin) + form.addRow("单笔委托上限(数量)", self.size_limit_spin) + form.addRow("总成交上限(笔)", self.trade_limit_spin) + form.addRow("活动委托上限(笔)", self.active_limit_spin) + form.addRow("合约撤单上限(笔)", self.cancel_limit_spin) + form.addRow(save_button) - # Vertical box layout - vbox = QtWidgets.QVBoxLayout() - vbox.addLayout(grid) - vbox.addLayout(hbox) - self.setLayout(vbox) - - # Connect signal to SpinBox - self.order_flow_limit.valueChanged.connect(self.risk_manager_engine.set_order_flow_limit) - self.order_flow_clear.valueChanged.connect(self.risk_manager_engine.set_order_flow_clear) - self.order_size_limit.valueChanged.connect(self.risk_manager_engine.set_order_size_limit) - self.trade_limit.valueChanged.connect(self.risk_manager_engine.set_trade_limit) - self.active_order_limit.valueChanged.connect(self.risk_manager_engine.set_active_order_limit) - self.order_cancel_limit.valueChanged.connect(self.risk_manager_engine.set_order_cancel_limit) - - # Connect signal to button - self.switch_button.clicked.connect(self.switch_engine_status) - clear_order_count_button.clicked.connect(self.risk_manager_engine.clear_order_flow_count) - clear_trade_count_button.clicked.connect(self.risk_manager_engine.clear_trade_count) - save_setting_button.clicked.connect(self.risk_manager_engine.save_setting) + self.setLayout(form) # Set Fix Size - self.setFixedSize(self.sizeHint()) + hint = self.sizeHint() + self.setFixedSize(hint.width() * 1.2, hint.height()) - def switch_engine_status(self): + def save_setting(self): """""" - self.risk_manager_engine.switch_engine_status() - self.update_engine_status() - - def update_engine_status(self): - """""" - if self.risk_manager_engine.active: - self.switch_button.setText("风控模块运行中") - self.order_flow_limit.setValue(self.risk_manager_engine.order_flow_limit) - self.order_flow_clear.setValue(self.risk_manager_engine.order_flow_clear) - self.order_size_limit.setValue(self.risk_manager_engine.order_size_limit) - self.trade_limit.setValue(self.risk_manager_engine.trade_limit) - self.active_order_limit.setValue(self.risk_manager_engine.active_order_limit) - self.order_cancel_limit.setValue(self.risk_manager_engine.order_cancel_limit) + active_text = self.active_combo.currentText() + if active_text == "启动": + active = True else: - self.switch_button.setText("风控模块未启动") - - self.risk_manager_engine.save_setting() + active = False + + setting = { + "active": active, + "order_flow_limit": self.flow_limit_spin.value(), + "order_flow_clear": self.flow_clear_spin.value(), + "order_size_limit": self.size_limit_spin.value(), + "trade_limit": self.trade_limit_spin.value(), + "active_order_limit": self.active_limit_spin.value(), + "order_cancel_limit": self.cancel_limit_spin.value(), + } + + self.rm_engine.update_setting(setting) + self.rm_engine.save_setting() + + self.close() + + def update_setting(self): + """""" + setting = self.rm_engine.get_setting() + if setting["active"]: + self.active_combo.setCurrentIndex(1) + else: + self.active_combo.setCurrentIndex(0) + + self.flow_limit_spin.setValue(setting["order_flow_limit"]) + self.flow_clear_spin.setValue(setting["order_flow_clear"]) + self.size_limit_spin.setValue(setting["order_size_limit"]) + self.trade_limit_spin.setValue(setting["trade_limit"]) + self.active_limit_spin.setValue(setting["active_order_limit"]) + self.cancel_limit_spin.setValue(setting["order_cancel_limit"]) + + def exec_(self): + """""" + self.update_setting() + super().exec_() class RiskManagerSpinBox(QtWidgets.QSpinBox): """""" - def __init__(self, value): + def __init__(self, value: int = 0): """""" - super(RiskManagerSpinBox, self).__init__() - + super().__init__() self.setMinimum(0) - self.setMaximum(1000000) + self.setMaximum(1000000) self.setValue(value) diff --git a/vnpy/trader/ui/mainwindow.py b/vnpy/trader/ui/mainwindow.py index 94abc4cc..a728885f 100644 --- a/vnpy/trader/ui/mainwindow.py +++ b/vnpy/trader/ui/mainwindow.py @@ -187,7 +187,7 @@ class MainWindow(QtWidgets.QMainWindow): if not dialog: dialog = ConnectDialog(self.main_engine, gateway_name) - dialog.exec() + dialog.exec_() def closeEvent(self, event): """ @@ -222,7 +222,7 @@ class MainWindow(QtWidgets.QMainWindow): self.widgets[name] = widget if isinstance(widget, QtWidgets.QDialog): - widget.exec() + widget.exec_() else: widget.show()