diff --git a/vnpy/app/cta_strategy/__init__.py b/vnpy/app/cta_strategy/__init__.py index 896d4d40..2bc3bf51 100644 --- a/vnpy/app/cta_strategy/__init__.py +++ b/vnpy/app/cta_strategy/__init__.py @@ -2,6 +2,7 @@ from pathlib import Path from vnpy.trader.app import BaseApp from .engine import CtaEngine +from .template import CtaTemplate from .base import APP_NAME diff --git a/vnpy/app/cta_strategy/engine.py b/vnpy/app/cta_strategy/engine.py index 9981601d..e3a70fa5 100644 --- a/vnpy/app/cta_strategy/engine.py +++ b/vnpy/app/cta_strategy/engine.py @@ -47,31 +47,32 @@ class CtaEngine(BaseEngine): event_engine, "CtaStrategy") - self._engine_type = EngineType.LIVE # live trading engine - self.setting_file = None # setting file object + self.engine_type = EngineType.LIVE # live trading engine + self.setting_file = None # setting file object - self._strategy_classes = {} # class_name: stategy_class - self._strategies = {} # name: strategy + self.classes = {} # class_name: stategy_class + self.strategies = {} # strategy_name: strategy - self._symbol_strategy_map = defaultdict(list) # vt_symbol: strategy list - self._orderid_strategy_map = {} # vt_orderid: strategy + self.symbol_strategy_map = defaultdict(list) # vt_symbol: strategy list + self.orderid_strategy_map = {} # vt_orderid: strategy + self.strategy_orderid_map = defaultdict( + set + ) # strategy_name: orderid list - self._active_orderids = defaultdict(set) # name: active orderid list - - self._stop_order_count = 0 # for generating stop_orderid - self._stop_orders = {} # stop_orderid: stop_order + 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.load_strategy_setting() self.register_event() self.write_log("CTA策略引擎初始化成功") def close(self): """""" - self.save_setting() + self.save_strategy_setting() def register_event(self): """""" @@ -83,26 +84,26 @@ class CtaEngine(BaseEngine): """""" tick = event.data - strategies = self._symbol_strategy_map[tick.vt_symbol] + strategies = self.symbol_strategy_map[tick.vt_symbol] if not strategies: return self.check_stop_order(tick) for strategy in strategies: - if strategy._inited: + if strategy.inited: self.call_strategy_func(strategy, strategy.on_tick, tick) def process_order_event(self, event: Event): """""" order = event.data - strategy = self._orderid_strategy_map.get(order.vt_orderid, None) + strategy = self.orderid_strategy_map.get(order.vt_orderid, None) if not strategy: return # Remove vt_orderid if order is no longer active. - vt_orderids = self._active_orderids[strategy.name] + vt_orderids = self.strategy_orderid_map[strategy.name] if order.vt_orderid in vt_orderids and not order.is_active(): vt_orderids.remove(order.vt_orderid) @@ -112,20 +113,20 @@ class CtaEngine(BaseEngine): """""" trade = event.data - strategy = self._orderid_strategy_map.get(trade.vt_orderid, None) + strategy = self.orderid_strategy_map.get(trade.vt_orderid, None) if not strategy: return if trade.direction == Direction.LONG: - strategy._pos += trade.volume + strategy.pos += trade.volume else: - strategy._pos -= trade.volume + strategy.pos -= trade.volume self.call_strategy_func(strategy, strategy.on_trade, trade) def check_stop_order(self, tick: TickData): """""" - for stop_order in self._stop_orders.values(): + for stop_order in self.stop_orders.values(): if stop_order.vt_symbol != tick.vt_symbol: continue @@ -165,9 +166,9 @@ class CtaEngine(BaseEngine): # Update stop order status if placed successfully if vt_orderid: # Remove from relation map. - self._stop_orders.pop(stop_order.stop_orderid) + self.stop_orders.pop(stop_order.stop_orderid) - vt_orderids = self._active_orderids[strategy.name] + vt_orderids = self.strategy_orderid_map[strategy.name] if stop_orderid in vt_orderids: vt_orderids.remove(stop_orderid) @@ -214,9 +215,9 @@ class CtaEngine(BaseEngine): ) # Save relationship between orderid and strategy. - self._orderid_strategy_map[vt_orderid] = strategy + self.orderid_strategy_map[vt_orderid] = strategy - vt_orderids = self._active_orderids[strategy.name] + vt_orderids = self.strategy_orderid_map[strategy.name] vt_orderids.add(vt_orderid) return vt_orderid @@ -231,9 +232,9 @@ class CtaEngine(BaseEngine): """ Send a new order. """ - self._stop_order_count += 1 + self.stop_order_count += 1 direction, offset = ORDER_CTA2VT[order_type] - stop_orderid = f"{STOPORDER_PREFIX}.{self._stop_order_count}" + stop_orderid = f"{STOPORDER_PREFIX}.{self.stop_order_count}" stop_order = StopOrder( vt_symbol=strategy.vt_symbol, @@ -245,9 +246,9 @@ class CtaEngine(BaseEngine): strategy=strategy ) - self._stop_orders[stop_orderid] = stop_order + self.stop_orders[stop_orderid] = stop_order - vt_orderids = self._active_orderids[strategy.name] + vt_orderids = self.strategy_orderid_map[strategy.name] vt_orderids.add(stop_orderid) self.call_strategy_func(strategy, strategy.on_stop_order, stop_order) @@ -270,15 +271,15 @@ class CtaEngine(BaseEngine): """ Cancel a local stop order. """ - stop_order = self._stop_orders.get(stop_orderid, None) + stop_order = self.stop_orders.get(stop_orderid, None) if not stop_order: return strategy = stop_order.strategy # Remove from relation map. - self._stop_orders.pop(stop_orderid) + self.stop_orders.pop(stop_orderid) - vt_orderids = self._active_orderids[strategy.name] + vt_orderids = self.strategy_orderid_map[strategy.name] if stop_orderid in vt_orderids: vt_orderids.remove(stop_orderid) @@ -314,7 +315,7 @@ class CtaEngine(BaseEngine): """ Cancel all active orders of a strategy. """ - vt_orderids = self._active_orderids[strategy.name] + vt_orderids = self.strategy_orderid_map[strategy.name] if not vt_orderids: return @@ -323,7 +324,7 @@ class CtaEngine(BaseEngine): def get_engine_type(self): """""" - return self._engine_type + return self.engine_type def call_strategy_func( self, @@ -340,116 +341,120 @@ class CtaEngine(BaseEngine): else: func() except Exception: - strategy._trading = False - strategy._inited = False + strategy.trading = False + strategy.inited = False msg = f"触发异常已停止\n{traceback.format_exc()}" self.write_log(msg, strategy) - def add_strategy(self, setting): + def add_strategy( + self, + class_name: str, + strategy_name: str, + vt_symbol: str, + setting: dict + ): """ Add a new strategy. """ - name = setting["name"] - if name in self._strategies: - self.write_log(f"创建策略失败,存在重名{name}") + if strategy_name in self.strategies: + self.write_log(f"创建策略失败,存在重名{strategy_name}") return - class_name = setting["class_name"] - strategy_class = self._strategy_classes[class_name] + strategy_class = self.classes[class_name] - strategy = strategy_class(self, setting) - self._strategies[name] = strategy + strategy = strategy_class(self, strategy_name, vt_symbol, setting) + self.strategies[strategy_name] = strategy # Add vt_symbol to strategy map. - strategies = self._symbol_strategy_map[strategy.vt_symbol] + strategies = self.symbol_strategy_map[vt_symbol] strategies.append(strategy) # Update to setting file. - self.update_setting(setting) + self.update_strategy_setting(strategy_name, setting) - self.put_strategy_event() + self.put_strategy_event(strategy) - def init_strategy(self, name): + def init_strategy(self, strategy_name: str): """ Init a strategy. """ - strategy = self._strategies[name] + strategy = self.strategies[strategy_name] self.call_strategy_func(strategy, strategy.on_init) - strategy._inited = True + strategy.inited = True # Subscribe market data contract = self.main_engine.get_contract(strategy.vt_symbol) if not contract: self.write_log(f"行情订阅失败,找不到合约{strategy.vt_symbol}", strategy) - self.put_strategy_event() + self.put_strategy_event(strategy) - def start_strategy(self, name): + def start_strategy(self, strategy_name: str): """ Start a strategy. """ - strategy = self._strategies[name] + strategy = self.strategies[strategy_name] self.call_strategy_func(strategy, strategy.on_start) - strategy._trading = True + strategy.trading = True - self.put_strategy_event() + self.put_strategy_event(strategy) - def stop_strategy(self, name): + def stop_strategy(self, strategy_name: str): """ Stop a strategy. """ - strategy = self._strategies[name] + strategy = self.strategies[strategy_name] self.call_strategy_func(strategy, strategy.on_start) - strategy._trading = False + strategy.trading = False - self.put_strategy_event() + self.put_strategy_event(strategy) - def edit_strategy(self, setting): + def edit_strategy(self, strategy_name: str, setting: dict): """ Edit parameters of a strategy. """ - name = setting["name"] - strategy = self._strategies[name] + strategy = self.strategies[strategy_name] + strategy.update_setting(setting) - for name in strategy.parameters: - setattr(strategy, name, setting[name]) - - self.update_setting(setting) + self.update_strategy_setting(strategy_name, setting) self.put_strategy_event(strategy) - def remove_strategy(self, name): + def remove_strategy(self, strategy_name: str): """ Remove a strategy. """ # Remove setting - self.remove_setting(name) + self.remove_strategy_setting(strategy_name) # Remove from symbol strategy map - strategy = self._strategies[name] - strategies = self._symbol_strategy_map[strategy.vt_symbol] + strategy = self.strategies[strategy_name] + strategies = self.symbol_strategy_map[strategy.vt_symbol] strategies.remove(strategy) # Remove from active orderid map - if name in self._active_orderids: - vt_orderids = self._active_orderids.pop(name) + if strategy_name in self.strategy_orderid_map: + vt_orderids = self.strategy_orderid_map.pop(strategy_name) # Remove vt_orderid strategy map for vt_orderid in vt_orderids: - self._orderid_strategy_map.pop(vt_orderid) + self.orderid_strategy_map.pop(vt_orderid) # Remove from strategies - self._strategies.pop(name) + self.strategies.pop(strategy_name) def load_strategy_class(self): """ Load strategy class from source code. """ path1 = Path(__file__).parent.joinpath("strategies") - self.load_strategy_class_from_folder(path1, __name__) + self.load_strategy_class_from_folder( + path1, + "vnpy.app.cta_strategy.strategies" + ) path2 = Path.cwd().joinpath("strategies") - self.load_strategy_class_from_folder(path2) + self.load_strategy_class_from_folder(path2, "strategies") def load_strategy_class_from_folder( self, @@ -460,8 +465,12 @@ class CtaEngine(BaseEngine): Load strategy class from certain folder. """ for dirpath, dirnames, filenames in os.walk(path): - for name in filenames: - module_name = ".".join([module_name, name.replace(".py", "")]) + for filename in filenames: + module_name = ".".join( + [module_name, + filename.replace(".py", + "")] + ) self.load_strategy_class_from_module(module_name) def load_strategy_class_from_module(self, module_name: str): @@ -473,8 +482,8 @@ class CtaEngine(BaseEngine): for name in dir(module): value = getattr(module, name) - if isinstance(value, CtaTemplate): - self._strategy_classes[value.__name__] = value + if issubclass(value, CtaTemplate) and value is not CtaTemplate: + self.classes[value.__name__] = value except: msg = f"策略文件{module_name}加载失败,触发异常:\n{traceback.format_exc()}" self.write_log(msg) @@ -483,13 +492,13 @@ class CtaEngine(BaseEngine): """ Return names of strategy classes loaded. """ - return list(self._strategy_classes.keys()) + return list(self.classes.keys()) def get_strategy_class_parameters(self, class_name: str): """ - Get default parameters of a strategy. + Get default parameters of a strategy class. """ - strategy_class = self._strategy_classes[class_name] + strategy_class = self.classes[class_name] parameters = {} for name in strategy_class.parameters: @@ -497,51 +506,67 @@ class CtaEngine(BaseEngine): return parameters + def get_strategy_parameters(self, strategy_name): + """ + Get parameters of a strategy. + """ + strategy = self.strategies[strategy_name] + return strategy.get_parameters() + def init_all_strategies(self): """ """ - for name in self._strategies.keys(): - self.init_strategy(name) + for strategy_name in self.strategies.keys(): + self.init_strategy(strategy_name) def start_all_strategies(self): """ """ - for name in self._strategies.keys(): - self.start_strategy(name) + for strategy_name in self.strategies.keys(): + self.start_strategy(strategy_name) def stop_all_strategies(self): """ """ - for name in self._strategies.keys(): - self.stop_strategy(name) + for strategy_name in self.strategies.keys(): + self.stop_strategy(strategy_name) - def load_setting(self): + def load_strategy_setting(self): """ Load setting file. """ - filepath = get_temp_path(self.filename) - self.setting_file = shelve.open(filename) - for setting in list(self.setting_file.values()): - self.add_strategy(setting) + filepath = str(get_temp_path(self.filename)) + self.setting_file = shelve.open(filepath) - def update_setting(self, setting: dict): + for tp in list(self.setting_file.values()): + class_name, strategy_name, vt_symbol, setting = tp + self.add_strategy(class_name, strategy_name, vt_symbol, setting) + + def update_strategy_setting(self, strategy_name: str, setting: dict): """ Update setting file. """ - self.setting_file[new_setting["name"]] = new_setting + strategy = self.strategies[strategy_name] + + self.setting_file[strategy_name] = ( + strategy.__class__.__name__, + strategy_name, + strategy.vt_symbol, + setting + ) self.setting_file.sync() - def remove_setting(self, name: str): + def remove_strategy_setting(self, strategy_name: str): """ Update setting file. """ - if name not in self.setting_file: + if strategy_name not in self.setting_file: return - self.setting_file.pop(name) + self.setting_file.pop(strategy_name) self.setting_file.sync() - def save_setting(self): + def save_strategy_setting(self): """ Save and close setting file. """ @@ -558,25 +583,7 @@ class CtaEngine(BaseEngine): """ Put an event to update strategy status. """ - parameters = {} - for name in strategy.parameters: - parameters[name] = getattr(strategy, name) - - variables = {} - for name in strategy.variables: - variables[name] = getattr(strategy, name) - - data = { - "name": name, - "class_name": strategy.__class__.__name__, - "inited": strategy._inited, - "trading": strategy._trading, - "pos": strategy._pos, - "author": strategy.author, - "vt_symbol": strategy.vt_symbol, - "parameters": parameters, - "variables": variables - } + data = strategy.get_data() event = Event(EVENT_CTA_STRATEGY, data) self.event_engine.put(event) @@ -585,7 +592,7 @@ class CtaEngine(BaseEngine): Create cta engine log event. """ if strategy: - msg = f"{strategy.name}: {msg}" + msg = f"{strategy.strategy_name}: {msg}" log = LogData(msg=msg, gateway_name="CtaStrategy") event = Event(type=EVENT_CTA_LOG, data=log) diff --git a/vnpy/app/cta_strategy/strategies/double_ma_strategy.py b/vnpy/app/cta_strategy/strategies/double_ma_strategy.py new file mode 100644 index 00000000..e292503e --- /dev/null +++ b/vnpy/app/cta_strategy/strategies/double_ma_strategy.py @@ -0,0 +1,23 @@ +from vnpy.app.cta_strategy import CtaTemplate + + +class DoubleMaStrategy(CtaTemplate): + + author = "用Python的交易员" + + fast_window = 10 + slow_window = 20.10 + + fast_ma = 0.0 + slow_ma = 0.0 + + parameters = ["fast_window", "slow_window"] + variables = ["fast_ma", "slow_ma"] + + def __init__(self, cta_engine, strategy_name, vt_symbol, setting): + """""" + super(DoubleMaStrategy, + self).__init__(cta_engine, + strategy_name, + vt_symbol, + setting) diff --git a/vnpy/app/cta_strategy/template.py b/vnpy/app/cta_strategy/template.py index 89a122b3..33ef5476 100644 --- a/vnpy/app/cta_strategy/template.py +++ b/vnpy/app/cta_strategy/template.py @@ -11,26 +11,82 @@ from .base import CtaOrderType, StopOrder class CtaTemplate(ABC): """""" - _inited = False - _trading = False - _pos = 0 - author = "" - vt_symbol = "" - parameters = [] variables = [] - def __init__(self, engine: BaseEngine, setting: dict): + def __init__( + self, + cta_engine: BaseEngine, + strategy_name: str, + vt_symbol: str, + setting: dict + ): """""" - self.engine = engine + self.cta_engine = cta_engine + self.strategy_name = strategy_name + self.vt_symbol = vt_symbol - self.vt_symbol = setting["vt_symbol"] + self.inited = False + self.trading = False + self.pos = 0 + self.variables.insert(0, "inited") + self.variables.insert(1, "trading") + self.variables.insert(2, "pos") + + self.update_setting(setting) + + def update_setting(self, setting: dict): + """ + Update strategy parameter wtih value in setting dict. + """ for name in self.parameters: if name in setting: setattr(self, name, setting[name]) + @classmethod + def get_class_parameters(cls): + """ + Get default parameters dict of strategy class. + """ + class_parameters = {} + for name in cls.parameters: + class_parameters[name] = getattr(cls, name) + return class_parameters + + def get_parameters(self): + """ + Get strategy parameters dict. + """ + strategy_parameters = {} + for name in self.parameters: + strategy_parameters[name] = getattr(self, name) + return strategy_parameters + + def get_variables(self): + """ + Get strategy variables dict. + """ + strategy_variables = {} + for name in self.variables: + strategy_variables[name] = getattr(self, name) + return strategy_variables + + def get_data(self): + """ + Get strategy data. + """ + strategy_data = { + "strategy_name": self.strategy_name, + "vt_symbol": self.vt_symbol, + "class_name": self.__class__.__name__, + "author": self.author, + "parameters": self.get_parameters(), + "variables": self.get_variables() + } + return strategy_data + def on_init(self): """ Callback when strategy is inited. @@ -107,34 +163,34 @@ class CtaTemplate(ABC): """ Send a new order. """ - return self.engine.send_order(self, order_type, price, volume, stop) + return self.cta_engine.send_order(self, order_type, price, volume, stop) def cancel_order(self, vt_orderid): """ Cancel an existing order. """ - self.engine.cancel_order(vt_orderid) + self.cta_engine.cancel_order(vt_orderid) def cancel_all(self): """ Cancel all orders sent by strategy. """ - self.engine.cancel_all(self) + self.cta_engine.cancel_all(self) def write_log(self, msg): """ Write a log message. """ - self.engine.write_log(self, msg) + self.cta_engine.write_log(self, msg) def get_engine_type(self): """ - Return whether the engine is backtesting or live trading. + Return whether the cta_engine is backtesting or live trading. """ - return self.engine.get_engine_type() + return self.cta_engine.get_engine_type() - def get_pos(self): + def put_event(self): """ - Return current net position of the strategy. + Put an strategy data event for ui update. """ - return self._pos + self.cta_engine.put_strategy_event(self) diff --git a/vnpy/app/cta_strategy/ui/widget.py b/vnpy/app/cta_strategy/ui/widget.py index bdc5c16a..b8ca175c 100644 --- a/vnpy/app/cta_strategy/ui/widget.py +++ b/vnpy/app/cta_strategy/ui/widget.py @@ -3,7 +3,7 @@ from typing import Any from vnpy.event import EventEngine, Event from vnpy.trader.engine import BaseEngine, MainEngine from vnpy.trader.ui import QtGui, QtWidgets, QtCore -from vnpy.trader.ui.widget import BaseMonitor, BaseCell, EnumCell, DirectionCell +from vnpy.trader.ui.widget import BaseMonitor, BaseCell, EnumCell, TimeCell, MsgCell from ..engine import CtaEngine from ..base import APP_NAME, EVENT_CTA_LOG, EVENT_CTA_STOPORDER, EVENT_CTA_STRATEGY @@ -21,12 +21,12 @@ class CtaManager(QtWidgets.QWidget): 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() + self.cta_engine.init_engine() + self.update_class_combo() def init_ui(self): """""" @@ -34,9 +34,6 @@ class CtaManager(QtWidgets.QWidget): # 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) @@ -51,6 +48,7 @@ class CtaManager(QtWidgets.QWidget): stop_button.clicked.connect(self.cta_engine.stop_all_strategies) self.scroll_layout = QtWidgets.QVBoxLayout() + self.scroll_layout.addStretch() scroll_widget = QtWidgets.QWidget() scroll_widget.setLayout(self.scroll_layout) @@ -61,15 +59,12 @@ class CtaManager(QtWidgets.QWidget): bottom_height = 300 - self.log_monitor = QtWidgets.QTextEdit() - self.log_monitor.setReadOnly(True) - self.log_monitor.setMaximumHeight(bottom_height) + self.log_monitor = LogMonitor(self.main_engine, self.event_engine) self.stop_order_monitor = StopOrderMonitor( self.main_engine, self.event_engine ) - self.stop_order_monitor.setMaximumHeight(bottom_height) # Set layout hbox1 = QtWidgets.QHBoxLayout() @@ -80,54 +75,50 @@ class CtaManager(QtWidgets.QWidget): hbox1.addWidget(start_button) hbox1.addWidget(stop_button) - hbox2 = QtWidgets.QHBoxLayout() - hbox2.addWidget(self.log_monitor) - hbox2.addWidget(self.stop_order_monitor) + 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.addWidget(scroll_area) - vbox.addLayout(hbox2) + 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_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"] + strategy_name = data["strategy_name"] - if name in self.managers: - manager = self.managers[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, name): + def remove_strategy(self, strategy_name): """""" - manager = self.managers[name] + manager = self.managers[strategy_name] manager.deleteLater() def add_strategy(self): @@ -137,20 +128,27 @@ class CtaManager(QtWidgets.QWidget): 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) + 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 show(self): """""" self.showMaximized() -class StrategyManager(QtWidgets.QWidget): +class StrategyManager(QtWidgets.QFrame): """ Manager for a strategy """ @@ -162,16 +160,22 @@ class StrategyManager(QtWidgets.QWidget): data: dict ): """""" + super(StrategyManager, self).__init__() + self.cta_manager = cta_manager self.cta_engine = cta_engine - self.name = data["name"] + self.strategy_name = data["strategy_name"] self._data = data self.init_ui() def init_ui(self): """""" + self.setMaximumHeight(200) + self.setFrameShape(self.Box) + self.setLineWidth(1) + init_button = QtWidgets.QPushButton("初始化") init_button.clicked.connect(self.init_strategy) @@ -187,27 +191,19 @@ class StrategyManager(QtWidgets.QWidget): 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']}") + strategy_name = self._data["strategy_name"] + vt_symbol = self._data["vt_symbol"] + class_name = self._data["class_name"] + author = self._data["author"] + + label_text = f"{strategy_name} - {vt_symbol} ({class_name} by {author})" + label = QtWidgets.QLabel(label_text) + label.setAlignment(QtCore.Qt.AlignCenter) 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) @@ -215,6 +211,7 @@ class StrategyManager(QtWidgets.QWidget): hbox.addWidget(remove_button) vbox = QtWidgets.QVBoxLayout() + vbox.addWidget(label) vbox.addLayout(hbox) vbox.addWidget(self.parameters_monitor) vbox.addWidget(self.variables_monitor) @@ -224,33 +221,39 @@ class StrategyManager(QtWidgets.QWidget): """""" 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) + self.cta_engine.init_strategy(self.strategy_name) def start_strategy(self): """""" - self.cta_engine.start_strategy(self.name) + self.cta_engine.start_strategy(self.strategy_name) def stop_strategy(self): """""" - self.cta_engine.stop_strategy(self.name) + self.cta_engine.stop_strategy(self.strategy_name) def edit_strategy(self): """""" - pass + vt_symbol = self._data["vt_symbol"] + class_name = self._data["class_name"] + strategy_name = self._data["strategy_name"] + + parameters = self.cta_engine.get_strategy_parameters(strategy_name) + editor = SettingEditor(parameters, strategy_name=strategy_name) + n = editor.exec_() + + if n == editor.Accepted: + setting = editor.get_setting() + self.cta_engine.edit_strategy(strategy_name, setting) def remove_strategy(self): """""" - self.cta_engine.remove_strategy(self.name) - self.cta_manager.remove_strategy(self.name) + self.cta_engine.remove_strategy(self.strategy_name) + self.cta_manager.remove_strategy(self.strategy_name) class DataMonitor(QtWidgets.QTableWidget): @@ -270,16 +273,22 @@ class DataMonitor(QtWidgets.QTableWidget): def init_ui(self): """""" labels = list(self._data.keys()) - self.setColumnCount(labels) + self.setColumnCount(len(labels)) self.setHorizontalHeaderLabels(labels) self.setRowCount(1) + self.verticalHeader().setSectionResizeMode( + QtWidgets.QHeaderView.Stretch + ) self.verticalHeader().setVisible(False) self.setEditTriggers(self.NoEditTriggers) - for column, name in enumerate(self._data.items()): + for column, name in enumerate(self._data.keys()): value = self._data[name] + cell = QtWidgets.QTableWidgetItem(str(value)) + cell.setTextAlignment(QtCore.Qt.AlignCenter) + self.setItem(0, column, cell) self.cells[name] = cell @@ -304,7 +313,7 @@ class StrategyCell(BaseCell): Set text using enum.constant.value. """ if content: - super(StrategyCell, self).set_content(content.name, data) + super(StrategyCell, self).set_content(content.strategy_name, data) class StopOrderMonitor(BaseMonitor): @@ -358,6 +367,56 @@ class StopOrderMonitor(BaseMonitor): } } + def init_ui(self): + """ + Stretch columns. + """ + super(StopOrderMonitor, self).init_ui() + + self.horizontalHeader().setSectionResizeMode( + QtWidgets.QHeaderView.Stretch + ) + + +class LogMonitor(BaseMonitor): + """ + Monitor for log data. + """ + event_type = EVENT_CTA_LOG + data_key = "" + sorting = False + + headers = { + "time": { + "display": "时间", + "cell": TimeCell, + "update": False + }, + "msg": { + "display": "信息", + "cell": MsgCell, + "update": False + } + } + + def init_ui(self): + """ + Stretch last column. + """ + super(LogMonitor, self).init_ui() + + self.horizontalHeader().setSectionResizeMode( + 1, + QtWidgets.QHeaderView.Stretch + ) + + def insert_new_row(self, data): + """ + Insert a new row at the top of table. + """ + super(LogMonitor, self).insert_new_row(data) + self.resizeRowToContents(0) + class SettingEditor(QtWidgets.QDialog): """ @@ -400,12 +459,19 @@ class SettingEditor(QtWidgets.QDialog): type_ = type(value) edit = QtWidgets.QLineEdit(str(value)) + if type_ is int: + validator = QtGui.QIntValidator() + edit.setValidator(validator) + elif type_ is float: + validator = QtGui.QDoubleValidator() + edit.setValidator(validator) + form.addRow(f"{name} {type_}", edit) self.edits[name] = (edit, type_) button = QtWidgets.QPushButton(button_text) - button.clicked.connect(self.accpet) + button.clicked.connect(self.accept) form.addRow(button) self.setLayout(form) diff --git a/vnpy/trader/ui/__init__.py b/vnpy/trader/ui/__init__.py index dcc8252c..665d6a59 100644 --- a/vnpy/trader/ui/__init__.py +++ b/vnpy/trader/ui/__init__.py @@ -1,5 +1,7 @@ import platform import ctypes +import traceback +import sys from pathlib import Path import qdarkstyle @@ -10,10 +12,23 @@ from ..setting import SETTINGS from ..utility import get_icon_path +def excepthook(exctype, value, tb): + """异常捕捉钩子""" + msg = ''.join(traceback.format_exception(exctype, value, tb)) + QtWidgets.QMessageBox.critical( + None, + u'Exception', + msg, + QtWidgets.QMessageBox.Ok + ) + + def create_qapp(): """ Create Qt Application. """ + sys.excepthook = excepthook + qapp = QtWidgets.QApplication([]) qapp.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) diff --git a/vnpy/trader/ui/widget.py b/vnpy/trader/ui/widget.py index feb48ddf..5532f572 100644 --- a/vnpy/trader/ui/widget.py +++ b/vnpy/trader/ui/widget.py @@ -164,6 +164,17 @@ class TimeCell(BaseCell): self._data = data +class MsgCell(BaseCell): + """ + Cell used for showing msg data. + """ + + def __init__(self, content: str, data: Any): + """""" + super(MsgCell, self).__init__(content, data) + self.setTextAlignment(QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + + class BaseMonitor(QtWidgets.QTableWidget): """ Monitor data update in VN Trader. @@ -422,7 +433,7 @@ class LogMonitor(BaseMonitor): }, "msg": { "display": "信息", - "cell": BaseCell, + "cell": MsgCell, "update": False }, "gateway_name": {