commit
3b18570254
@ -25,6 +25,7 @@ from vnpy.app.csv_loader import CsvLoaderApp
|
|||||||
from vnpy.app.algo_trading import AlgoTradingApp
|
from vnpy.app.algo_trading import AlgoTradingApp
|
||||||
from vnpy.app.cta_backtester import CtaBacktesterApp
|
from vnpy.app.cta_backtester import CtaBacktesterApp
|
||||||
from vnpy.app.data_recorder import DataRecorderApp
|
from vnpy.app.data_recorder import DataRecorderApp
|
||||||
|
from vnpy.app.risk_manager import RiskManagerApp
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@ -55,6 +56,7 @@ def main():
|
|||||||
main_engine.add_app(CsvLoaderApp)
|
main_engine.add_app(CsvLoaderApp)
|
||||||
main_engine.add_app(AlgoTradingApp)
|
main_engine.add_app(AlgoTradingApp)
|
||||||
main_engine.add_app(DataRecorderApp)
|
main_engine.add_app(DataRecorderApp)
|
||||||
|
main_engine.add_app(RiskManagerApp)
|
||||||
|
|
||||||
main_window = MainWindow(main_engine, event_engine)
|
main_window = MainWindow(main_engine, event_engine)
|
||||||
main_window.showMaximized()
|
main_window.showMaximized()
|
||||||
|
14
vnpy/app/risk_manager/__init__.py
Normal file
14
vnpy/app/risk_manager/__init__.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from vnpy.trader.app import BaseApp
|
||||||
|
from .engine import RiskManagerEngine, APP_NAME
|
||||||
|
|
||||||
|
|
||||||
|
class RiskManagerApp(BaseApp):
|
||||||
|
""""""
|
||||||
|
app_name = APP_NAME
|
||||||
|
app_module = __module__
|
||||||
|
app_path = Path(__file__).parent
|
||||||
|
display_name = "交易风控"
|
||||||
|
engine_class = RiskManagerEngine
|
||||||
|
widget_name = "RiskManager"
|
||||||
|
icon_name = "rm.ico"
|
178
vnpy/app/risk_manager/engine.py
Normal file
178
vnpy/app/risk_manager/engine.py
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
""""""
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
from vnpy.trader.object import OrderRequest, LogData
|
||||||
|
from vnpy.event import Event, EventEngine, EVENT_TIMER
|
||||||
|
from vnpy.trader.engine import BaseEngine, MainEngine
|
||||||
|
from vnpy.trader.event import EVENT_TRADE, EVENT_ORDER, EVENT_LOG
|
||||||
|
from vnpy.trader.constant import Status
|
||||||
|
from vnpy.trader.utility import load_json, save_json
|
||||||
|
|
||||||
|
|
||||||
|
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 = 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):
|
||||||
|
""""""
|
||||||
|
result = self.check_risk(req, gateway_name)
|
||||||
|
if not result:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
return self._send_order(req, gateway_name)
|
||||||
|
|
||||||
|
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,
|
||||||
|
"order_flow_limit": self.order_flow_limit,
|
||||||
|
"order_flow_clear": self.order_flow_clear,
|
||||||
|
"order_size_limit": self.order_size_limit,
|
||||||
|
"trade_limit": self.trade_limit,
|
||||||
|
"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
|
||||||
|
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
|
||||||
|
|
||||||
|
if self.order_flow_timer >= self.order_flow_clear:
|
||||||
|
self.order_flow_count = 0
|
||||||
|
self.order_flow_timer = 0
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
def check_risk(self, req: OrderRequest, gateway_name: str):
|
||||||
|
""""""
|
||||||
|
if not self.active:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Check order volume
|
||||||
|
if req.volume <= 0:
|
||||||
|
self.write_log("委托数量必须大于0")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if 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_log(
|
||||||
|
f"今日总成交合约数量{self.trade_count},超过限制{self.trade_limit}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check flow count
|
||||||
|
if self.order_flow_count >= 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_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_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
|
1
vnpy/app/risk_manager/ui/__init__.py
Normal file
1
vnpy/app/risk_manager/ui/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .widget import RiskManager
|
BIN
vnpy/app/risk_manager/ui/rm.ico
Normal file
BIN
vnpy/app/risk_manager/ui/rm.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 59 KiB |
107
vnpy/app/risk_manager/ui/widget.py
Normal file
107
vnpy/app/risk_manager/ui/widget.py
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
from vnpy.event import EventEngine
|
||||||
|
from vnpy.trader.engine import MainEngine
|
||||||
|
from vnpy.trader.ui import QtWidgets
|
||||||
|
from ..engine import APP_NAME
|
||||||
|
|
||||||
|
|
||||||
|
class RiskManager(QtWidgets.QDialog):
|
||||||
|
""""""
|
||||||
|
|
||||||
|
def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
|
||||||
|
""""""
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.main_engine = main_engine
|
||||||
|
self.event_engine = event_engine
|
||||||
|
self.rm_engine = main_engine.get_engine(APP_NAME)
|
||||||
|
|
||||||
|
self.init_ui()
|
||||||
|
|
||||||
|
def init_ui(self):
|
||||||
|
""""""
|
||||||
|
self.setWindowTitle("交易风控")
|
||||||
|
|
||||||
|
# Create widgets
|
||||||
|
self.active_combo = QtWidgets.QComboBox()
|
||||||
|
self.active_combo.addItems(["停止", "启动"])
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
save_button = QtWidgets.QPushButton("保存")
|
||||||
|
save_button.clicked.connect(self.save_setting)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
self.setLayout(form)
|
||||||
|
|
||||||
|
# Set Fix Size
|
||||||
|
hint = self.sizeHint()
|
||||||
|
self.setFixedSize(hint.width() * 1.2, hint.height())
|
||||||
|
|
||||||
|
def save_setting(self):
|
||||||
|
""""""
|
||||||
|
active_text = self.active_combo.currentText()
|
||||||
|
if active_text == "启动":
|
||||||
|
active = True
|
||||||
|
else:
|
||||||
|
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: int = 0):
|
||||||
|
""""""
|
||||||
|
super().__init__()
|
||||||
|
self.setMinimum(0)
|
||||||
|
self.setMaximum(1000000)
|
||||||
|
self.setValue(value)
|
@ -187,7 +187,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
if not dialog:
|
if not dialog:
|
||||||
dialog = ConnectDialog(self.main_engine, gateway_name)
|
dialog = ConnectDialog(self.main_engine, gateway_name)
|
||||||
|
|
||||||
dialog.exec()
|
dialog.exec_()
|
||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
"""
|
"""
|
||||||
@ -222,7 +222,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
self.widgets[name] = widget
|
self.widgets[name] = widget
|
||||||
|
|
||||||
if isinstance(widget, QtWidgets.QDialog):
|
if isinstance(widget, QtWidgets.QDialog):
|
||||||
widget.exec()
|
widget.exec_()
|
||||||
else:
|
else:
|
||||||
widget.show()
|
widget.show()
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user