diff --git a/vnpy/app/cta_strategy/__init__.py b/vnpy/app/cta_strategy/__init__.py index 66a22cb8..896d4d40 100644 --- a/vnpy/app/cta_strategy/__init__.py +++ b/vnpy/app/cta_strategy/__init__.py @@ -2,11 +2,12 @@ from pathlib import Path from vnpy.trader.app import BaseApp from .engine import CtaEngine +from .base import APP_NAME class CtaStrategyApp(BaseApp): """""" - app_name = "CtaStrategy" + app_name = APP_NAME app_module = __module__ app_path = Path(__file__).parent display_name = "CTA策略" diff --git a/vnpy/app/cta_strategy/base.py b/vnpy/app/cta_strategy/base.py index f9345c04..a7ce8a74 100644 --- a/vnpy/app/cta_strategy/base.py +++ b/vnpy/app/cta_strategy/base.py @@ -8,6 +8,7 @@ from typing import Any from vnpy.trader.constant import Direction, Offset +APP_NAME = "CtaStrategy" STOPORDER_PREFIX = "STOP." diff --git a/vnpy/app/cta_strategy/engine.py b/vnpy/app/cta_strategy/engine.py index b363140a..9981601d 100644 --- a/vnpy/app/cta_strategy/engine.py +++ b/vnpy/app/cta_strategy/engine.py @@ -19,6 +19,7 @@ from vnpy.trader.object import ( ) from vnpy.trader.event import EVENT_TICK, EVENT_ORDER, EVENT_TRADE from vnpy.trader.constant import Direction, Offset, Exchange, PriceType +from vnpy.trader.utility import get_temp_path from .template import CtaTemplate from .base import ( @@ -60,9 +61,13 @@ class CtaEngine(BaseEngine): self._stop_order_count = 0 # for generating stop_orderid self._stop_orders = {} # stop_orderid: stop_order + def init_engine(self): + """ + """ self.load_strategy_class() self.load_setting() self.register_event() + self.write_log("CTA策略引擎初始化成功") def close(self): """""" @@ -410,6 +415,7 @@ class CtaEngine(BaseEngine): for name in strategy.parameters: setattr(strategy, name, setting[name]) + self.update_setting(setting) self.put_strategy_event(strategy) def remove_strategy(self, name): @@ -440,7 +446,7 @@ class CtaEngine(BaseEngine): Load strategy class from source code. """ path1 = Path(__file__).parent.joinpath("strategies") - self.load_strategy_class_from_folder(path1, __module__) + self.load_strategy_class_from_folder(path1, __name__) path2 = Path.cwd().joinpath("strategies") self.load_strategy_class_from_folder(path2) @@ -513,7 +519,8 @@ class CtaEngine(BaseEngine): """ Load setting file. """ - self.setting_file = shelve.open(self.filename) + filepath = get_temp_path(self.filename) + self.setting_file = shelve.open(filename) for setting in list(self.setting_file.values()): self.add_strategy(setting) @@ -561,6 +568,7 @@ class CtaEngine(BaseEngine): data = { "name": name, + "class_name": strategy.__class__.__name__, "inited": strategy._inited, "trading": strategy._trading, "pos": strategy._pos, diff --git a/vnpy/app/cta_strategy/template.py b/vnpy/app/cta_strategy/template.py index 48ec5d42..89a122b3 100644 --- a/vnpy/app/cta_strategy/template.py +++ b/vnpy/app/cta_strategy/template.py @@ -26,6 +26,7 @@ class CtaTemplate(ABC): self.engine = engine self.vt_symbol = setting["vt_symbol"] + for name in self.parameters: if name in setting: setattr(self, name, setting[name]) diff --git a/vnpy/app/cta_strategy/ui/widget.py b/vnpy/app/cta_strategy/ui/widget.py index 541bc237..bdc5c16a 100644 --- a/vnpy/app/cta_strategy/ui/widget.py +++ b/vnpy/app/cta_strategy/ui/widget.py @@ -1,13 +1,425 @@ -from vnpy.event import EventEngine +from typing import Any + +from vnpy.event import EventEngine, Event from vnpy.trader.engine import BaseEngine, MainEngine -from vnpy.trader.ui import QtGui, QtWidgets +from vnpy.trader.ui import QtGui, QtWidgets, QtCore +from vnpy.trader.ui.widget import BaseMonitor, BaseCell, EnumCell, DirectionCell + +from ..engine import CtaEngine +from ..base import APP_NAME, EVENT_CTA_LOG, EVENT_CTA_STOPORDER, EVENT_CTA_STRATEGY 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 \ No newline at end of file + self.event_engine = event_engine + self.cta_engine = main_engine.get_engine(APP_NAME) + + self.cta_engine.init_engine() + + self.managers = {} + + self.init_ui() + self.register_event() + + def init_ui(self): + """""" + self.setWindowTitle("CTA策略") + + # Create widgets + self.class_combo = QtWidgets.QComboBox() + self.class_combo.addItems( + self.cta_engine.get_all_strategy_class_names() + ) + + add_button = QtWidgets.QPushButton("添加策略") + add_button.clicked.connect(self.add_strategy) + + init_button = QtWidgets.QPushButton("全部初始化") + init_button.clicked.connect(self.cta_engine.init_all_strategies) + + start_button = QtWidgets.QPushButton("全部启动") + start_button.clicked.connect(self.cta_engine.start_all_strategies) + + stop_button = QtWidgets.QPushButton("全部停止") + stop_button.clicked.connect(self.cta_engine.stop_all_strategies) + + self.scroll_layout = QtWidgets.QVBoxLayout() + + scroll_widget = QtWidgets.QWidget() + scroll_widget.setLayout(self.scroll_layout) + + scroll_area = QtWidgets.QScrollArea() + scroll_area.setWidgetResizable(True) + scroll_area.setWidget(scroll_widget) + + bottom_height = 300 + + self.log_monitor = QtWidgets.QTextEdit() + self.log_monitor.setReadOnly(True) + self.log_monitor.setMaximumHeight(bottom_height) + + self.stop_order_monitor = StopOrderMonitor( + self.main_engine, + self.event_engine + ) + self.stop_order_monitor.setMaximumHeight(bottom_height) + + # 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) + + hbox2 = QtWidgets.QHBoxLayout() + hbox2.addWidget(self.log_monitor) + hbox2.addWidget(self.stop_order_monitor) + + vbox = QtWidgets.QVBoxLayout() + vbox.addLayout(hbox1) + vbox.addWidget(scroll_area) + vbox.addLayout(hbox2) + + self.setLayout(vbox) + + def register_event(self): + """""" + self.signal_log.connect(self.process_log_event) + self.signal_strategy.connect(self.process_strategy_event) + + self.event_engine.register(EVENT_CTA_LOG, self.signal_log.emit) + self.event_engine.register( + EVENT_CTA_STRATEGY, + self.signal_strategy.emit + ) + + def process_log_event(self, event): + """ + Update log output. + """ + log = event.data + time = log.time.strftime("%H:%M:S") + msg = f"{time}:\t{log.msg}" + self.log_monitor.append(msg) + + def process_strategy_event(self, event): + """ + Update strategy status onto its monitor. + """ + data = event.data + name = data["name"] + + if name in self.managers: + manager = self.managers[name] + manager.update_data(data) + else: + manager = StrategyManager(self, self.cta_engine, data) + self.scroll_layout.insertWidget(0, manager) + + def remove_strategy(self, name): + """""" + manager = self.managers[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() + self.cta_engine.add_strategy(setting) + + def show(self): + """""" + self.showMaximized() + + +class StrategyManager(QtWidgets.QWidget): + """ + Manager for a strategy + """ + + def __init__( + self, + cta_manager: CtaManager, + cta_engine: CtaEngine, + data: dict + ): + """""" + self.cta_manager = cta_manager + self.cta_engine = cta_engine + + self.name = data["name"] + self._data = data + + self.init_ui() + + def init_ui(self): + """""" + init_button = QtWidgets.QPushButton("初始化") + init_button.clicked.connect(self.init_strategy) + + start_button = QtWidgets.QPushButton("启动") + start_button.clicked.connect(self.start_strategy) + + stop_button = QtWidgets.QPushButton("停止") + stop_button.clicked.connect(self.stop_strategy) + + edit_button = QtWidgets.QPushButton("编辑") + edit_button.clicked.connect(self.edit_strategy) + + remove_button = QtWidgets.QPushButton("移除") + remove_button.clicked.connect(self.remove_strategy) + + self.name_label = QtWidgets.QLabel(f"策略名:{self._data['name']}") + self.class_label = QtWidgets.QLabel(f"策略类:{self._data['class_name']}") + self.symbol_label = QtWidgets.QLabel(f"代码:{self._data['vt_symbol']}") + self.author_label = QtWidgets.QLabel(f"作者:{self._data['author']}") + self.inited_label = QtWidgets.QLabel(f"inited:{self._data['inited']}") + self.trading_label = QtWidgets.QLabel( + f"trading:{self._data['trading']}" + ) + self.pos_label = QtWidgets.QLabel(f"pos:{self._data['pos']}") + + self.parameters_monitor = DataMonitor(self._data["parameters"]) + self.variables_monitor = DataMonitor(self._data["variables"]) + + hbox = QtWidgets.QHBoxLayout() + hbox.addWidget(self.name_label) + hbox.addWidget(self.class_label) + hbox.addWidget(self.symbol_label) + hbox.addWidget(self.author_label) + hbox.addWidget(self.inited_label) + hbox.addWidget(self.trading_label) + hbox.addWidget(self.pos_label) + hbox.addWidget(init_button) + hbox.addWidget(start_button) + hbox.addWidget(stop_button) + hbox.addWidget(edit_button) + hbox.addWidget(remove_button) + + vbox = QtWidgets.QVBoxLayout() + vbox.addLayout(hbox) + vbox.addWidget(self.parameters_monitor) + vbox.addWidget(self.variables_monitor) + self.setLayout(vbox) + + def update_data(self, data: dict): + """""" + self._data = data + + self.inited_label.setText(f"inited:{data['inited']}") + self.trading_label.setText(f"trading:{data['trading']}") + self.pos_label.setText(f"pos:{data['pos']}") + + self.parameters_monitor.update_data(data["parameters"]) + self.variables_monitor.update_data(data["variables"]) + + def init_strategy(self): + """""" + self.cta_engine.init_strategy(self.name) + + def start_strategy(self): + """""" + self.cta_engine.start_strategy(self.name) + + def stop_strategy(self): + """""" + self.cta_engine.stop_strategy(self.name) + + def edit_strategy(self): + """""" + pass + + def remove_strategy(self): + """""" + self.cta_engine.remove_strategy(self.name) + self.cta_manager.remove_strategy(self.name) + + +class DataMonitor(QtWidgets.QTableWidget): + """ + Table monitor for parameters and variables. + """ + + def __init__(self, data: dict): + """""" + super(DataMonitor, self).__init__() + + self._data = data + self.cells = {} + + self.init_ui() + + def init_ui(self): + """""" + labels = list(self._data.keys()) + self.setColumnCount(labels) + self.setHorizontalHeaderLabels(labels) + + self.setRowCount(1) + self.verticalHeader().setVisible(False) + self.setEditTriggers(self.NoEditTriggers) + + for column, name in enumerate(self._data.items()): + value = self._data[name] + cell = QtWidgets.QTableWidgetItem(str(value)) + self.setItem(0, column, cell) + self.cells[name] = cell + + def update_data(self, data: dict): + """""" + for name, value in data.items(): + cell = self.cells[name] + cell.setText(str(value)) + + +class StrategyCell(BaseCell): + """ + Cell used for showing strategy name. + """ + + def __init__(self, content: str, data: Any): + """""" + super(StrategyCell, self).__init__(content, data) + + def set_content(self, content: Any, data: Any): + """ + Set text using enum.constant.value. + """ + if content: + super(StrategyCell, self).set_content(content.name, data) + + +class StopOrderMonitor(BaseMonitor): + """ + Monitor for local stop order. + """ + event_type = EVENT_CTA_STOPORDER + data_key = "stop_orderid" + sorting = True + + headers = { + "stop_orderid": { + "display": "停止委托号", + "cell": BaseCell, + "update": False + }, + "vt_orderid": { + "display": "限价委托号", + "cell": BaseCell, + "update": True + }, + "vt_symbol": { + "display": "代码", + "cell": BaseCell, + "update": False + }, + "order_type": { + "display": "类型", + "cell": EnumCell, + "update": False + }, + "price": { + "display": "价格", + "cell": BaseCell, + "update": False + }, + "volume": { + "display": "数量", + "cell": BaseCell, + "update": True + }, + "status": { + "display": "状态", + "cell": EnumCell, + "update": True + }, + "strategy": { + "display": "策略名", + "cell": StrategyCell, + "update": True + } + } + + +class SettingEditor(QtWidgets.QDialog): + """ + For creating new strategy and editing strategy parameters. + """ + + def __init__( + self, + parameters: dict, + strategy_name: str = "", + class_name: str = "" + ): + """""" + super(SettingEditor, self).__init__() + + self.parameters = parameters + self.strategy_name = strategy_name + self.class_name = class_name + + self.edits = {} + + self.init_ui() + + def init_ui(self): + """""" + form = QtWidgets.QFormLayout() + + # Add vt_symbol and name edit if add new strategy + if self.class_name: + self.setWindowTitle(f"添加策略:{self.class_name}") + button_text = "添加" + parameters = {"strategy_name": "", "vt_symbol": ""} + parameters.update(self.parameters) + else: + self.setWindowTitle(f"参数编辑:{self.strategy_name}") + button_text = "确定" + parameters = self.parameters + + for name, value in parameters.items(): + type_ = type(value) + + edit = QtWidgets.QLineEdit(str(value)) + form.addRow(f"{name} {type_}", edit) + + self.edits[name] = (edit, type_) + + button = QtWidgets.QPushButton(button_text) + button.clicked.connect(self.accpet) + form.addRow(button) + + self.setLayout(form) + + def get_setting(self): + """""" + setting = {} + + if self.class_name: + setting["class_name"] = self.class_name + + for name, tp in self.edits.items(): + edit, type_ = tp + value = type_(edit.text()) + setting[name] = value + + return setting diff --git a/vnpy/trader/engine.py b/vnpy/trader/engine.py index c39700e8..dc9c6cdf 100644 --- a/vnpy/trader/engine.py +++ b/vnpy/trader/engine.py @@ -93,9 +93,17 @@ class MainEngine: gateway = self.gateways.get(gateway_name, None) if not gateway: self.write_log(f"找不到底层接口:{gateway_name}") - return None return gateway + def get_engine(self, engine_name: str): + """ + Return engine object by name. + """ + engine = self.engines.get(engine_name, None) + if not engine: + self.write_log(f"找不到引擎:{engine_name}") + return engine + def get_default_setting(self, gateway_name: str): """ Get default setting dict of a specific gateway.