From 41bcf7b605a6a1f18b0567ae0f6d2692dd376a52 Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Sun, 2 Jun 2019 14:51:15 +0800 Subject: [PATCH] [Mod] change RiskManager to use QDialog --- tests/trader/run.py | 2 + vnpy/app/risk_manager/__init__.py | 4 +- vnpy/app/risk_manager/engine.py | 169 ++++++++++------------ vnpy/app/risk_manager/ui/risk_manager.ico | Bin 67646 -> 0 bytes vnpy/app/risk_manager/ui/rm.ico | Bin 0 -> 59966 bytes vnpy/app/risk_manager/ui/widget.py | 149 +++++++++---------- vnpy/trader/ui/mainwindow.py | 4 +- 7 files changed, 152 insertions(+), 176 deletions(-) delete mode 100644 vnpy/app/risk_manager/ui/risk_manager.ico create mode 100644 vnpy/app/risk_manager/ui/rm.ico 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 4b376934c3f7d350ca3577567c6e2a39ef2812ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67646 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 I4|O^Je^F4ot^fc4 diff --git a/vnpy/app/risk_manager/ui/rm.ico b/vnpy/app/risk_manager/ui/rm.ico new file mode 100644 index 0000000000000000000000000000000000000000..751b82558e4cd567162afefd3e5e3255c8003487 GIT binary patch literal 59966 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