From f97f090a2536884b08c3f7ef627b9c20ea53ec5a Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Fri, 18 Jan 2019 09:35:35 +0800 Subject: [PATCH] [Add] BaseApp for app development --- tests/trader/run.py | 4 ++++ vnpy/app/cta_strategy/__init__.py | 15 +++++++++++++++ vnpy/app/cta_strategy/backtesting.py | 0 vnpy/app/cta_strategy/engine.py | 13 +++++++++++++ vnpy/app/cta_strategy/template.py | 11 +++++++++++ vnpy/app/cta_strategy/ui/__init__.py | 1 + vnpy/app/cta_strategy/ui/cta.ico | Bin 0 -> 67646 bytes vnpy/app/cta_strategy/ui/widget.py | 13 +++++++++++++ vnpy/trader/app.py | 15 ++++++++++----- vnpy/trader/engine.py | 16 ++++++++++++++++ vnpy/trader/ui/__init__.py | 2 +- vnpy/trader/ui/mainwindow.py | 25 +++++++++++++++++++++---- vnpy/trader/utility.py | 2 +- 13 files changed, 106 insertions(+), 11 deletions(-) create mode 100644 vnpy/app/cta_strategy/__init__.py create mode 100644 vnpy/app/cta_strategy/backtesting.py create mode 100644 vnpy/app/cta_strategy/engine.py create mode 100644 vnpy/app/cta_strategy/template.py create mode 100644 vnpy/app/cta_strategy/ui/__init__.py create mode 100644 vnpy/app/cta_strategy/ui/cta.ico create mode 100644 vnpy/app/cta_strategy/ui/widget.py diff --git a/tests/trader/run.py b/tests/trader/run.py index e6427744..67164438 100644 --- a/tests/trader/run.py +++ b/tests/trader/run.py @@ -5,6 +5,8 @@ from vnpy.gateway.ib import IbGateway from vnpy.gateway.futu import FutuGateway from vnpy.gateway.bitmex import BitmexGateway +from vnpy.app.cta_strategy import CtaStrategyApp + import os import logging import time @@ -21,6 +23,8 @@ def main(): main_engine.add_gateway(FutuGateway) main_engine.add_gateway(BitmexGateway) + main_engine.add_app(CtaStrategyApp) + main_window = MainWindow(main_engine, event_engine) main_window.showMaximized() diff --git a/vnpy/app/cta_strategy/__init__.py b/vnpy/app/cta_strategy/__init__.py new file mode 100644 index 00000000..f4204b5c --- /dev/null +++ b/vnpy/app/cta_strategy/__init__.py @@ -0,0 +1,15 @@ +from pathlib import Path + +from vnpy.trader.app import BaseApp +from .cta_engine import CtaEngine + + +class CtaStrategyApp(BaseApp): + """""" + app_name = "CtaStrategy" + app_module = __module__ + app_path = Path(__file__).parent + display_name = "CTA策略" + engine_class = CtaEngine + widget_name = "CtaManager" + icon_name = "cta.ico" \ No newline at end of file diff --git a/vnpy/app/cta_strategy/backtesting.py b/vnpy/app/cta_strategy/backtesting.py new file mode 100644 index 00000000..e69de29b diff --git a/vnpy/app/cta_strategy/engine.py b/vnpy/app/cta_strategy/engine.py new file mode 100644 index 00000000..bb0b0f2e --- /dev/null +++ b/vnpy/app/cta_strategy/engine.py @@ -0,0 +1,13 @@ +"""""" + +from vnpy.event import EventEngine +from vnpy.trader.engine import BaseEngine, MainEngine + + +class CtaEngine(BaseEngine): + + def __init__(self, main_engine: MainEngine, event_engine: EventEngine): + super(CtaEngine, + self).__init__(main_engine, + event_engine, + "CtaStrategy") diff --git a/vnpy/app/cta_strategy/template.py b/vnpy/app/cta_strategy/template.py new file mode 100644 index 00000000..56e90015 --- /dev/null +++ b/vnpy/app/cta_strategy/template.py @@ -0,0 +1,11 @@ +"""""" + +from abc import ABC + + +class CtaTemplate(ABC): + """""" + + def __init__(self, engine): + """""" + self.engine = engine diff --git a/vnpy/app/cta_strategy/ui/__init__.py b/vnpy/app/cta_strategy/ui/__init__.py new file mode 100644 index 00000000..ce7bb539 --- /dev/null +++ b/vnpy/app/cta_strategy/ui/__init__.py @@ -0,0 +1 @@ +from .widget import CtaManager \ No newline at end of file diff --git a/vnpy/app/cta_strategy/ui/cta.ico b/vnpy/app/cta_strategy/ui/cta.ico new file mode 100644 index 0000000000000000000000000000000000000000..25cbaa73acb29a85a55ff2f27fd2e73996a0e6d1 GIT binary patch literal 67646 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 literal 0 HcmV?d00001 diff --git a/vnpy/app/cta_strategy/ui/widget.py b/vnpy/app/cta_strategy/ui/widget.py new file mode 100644 index 00000000..541bc237 --- /dev/null +++ b/vnpy/app/cta_strategy/ui/widget.py @@ -0,0 +1,13 @@ +from vnpy.event import EventEngine +from vnpy.trader.engine import BaseEngine, MainEngine +from vnpy.trader.ui import QtGui, QtWidgets + + +class CtaManager(QtWidgets.QWidget): + """""" + + 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 diff --git a/vnpy/trader/app.py b/vnpy/trader/app.py index e01bb50c..e78dc771 100644 --- a/vnpy/trader/app.py +++ b/vnpy/trader/app.py @@ -1,11 +1,16 @@ +"""""" + from abc import ABC class BaseApp(ABC): """ - Abstract class for app. + Absstract class for app. """ - - app_name = '' - app_engine = None - app_ui = None \ No newline at end of file + app_name = "" # Unique name used for creating engine and widget + app_module = "" # App module string used in import_module + app_path = "" # Absolute path of app folder + display_name = "" # Name for display on the menu. + engine_class = None # App engine class + widget_name = "" # Class name of app widget + icon_name = "" # Icon file name of app widget diff --git a/vnpy/trader/engine.py b/vnpy/trader/engine.py index 2eff1943..c39700e8 100644 --- a/vnpy/trader/engine.py +++ b/vnpy/trader/engine.py @@ -25,6 +25,7 @@ from .object import LogData, SubscribeRequest, OrderRequest, CancelRequest from .utility import Singleton, get_temp_path from .setting import SETTINGS from .gateway import BaseGateway +from .app import BaseApp class MainEngine: @@ -60,6 +61,15 @@ class MainEngine: gateway = gateway_class(self.event_engine) self.gateways[gateway.gateway_name] = gateway + def add_app(self, app_class: BaseApp): + """ + Add app. + """ + app = app_class() + self.apps[app.app_name] = app + + self.add_engine(app.engine_class) + def init_engines(self): """ Init all engines. @@ -101,6 +111,12 @@ class MainEngine: """ return list(self.gateways.keys()) + def get_all_apps(self): + """ + Get all app objects. + """ + return list(self.apps.values()) + def connect(self, setting: dict, gateway_name: str): """ Start connection of a specific gateway. diff --git a/vnpy/trader/ui/__init__.py b/vnpy/trader/ui/__init__.py index 4630b75c..dcc8252c 100644 --- a/vnpy/trader/ui/__init__.py +++ b/vnpy/trader/ui/__init__.py @@ -3,7 +3,7 @@ import ctypes from pathlib import Path import qdarkstyle -from PyQt5 import QtWidgets, QtGui +from PyQt5 import QtWidgets, QtGui, QtCore from .mainwindow import MainWindow from ..setting import SETTINGS diff --git a/vnpy/trader/ui/mainwindow.py b/vnpy/trader/ui/mainwindow.py index 58d84b71..f436efef 100644 --- a/vnpy/trader/ui/mainwindow.py +++ b/vnpy/trader/ui/mainwindow.py @@ -4,6 +4,7 @@ Implements main window of VN Trader. from functools import partial from typing import Callable +from importlib import import_module from PyQt5 import QtWidgets, QtCore, QtGui @@ -85,6 +86,22 @@ class MainWindow(QtWidgets.QMainWindow): self.add_menu_action(sys_menu, "退出", "exit.ico", self.close) # App menu + all_apps = self.main_engine.get_all_apps() + for app in all_apps: + try: + ui_module = import_module(app.app_module + ".ui") + widget_class = getattr(ui_module, app.widget_name) + except ImportError: + continue + + func = partial(self.open_widget, widget_class, app.app_name) + icon_path = str(app.app_path.joinpath("ui", app.icon_name)) + self.add_menu_action( + app_menu, + f"打开{app.display_name}", + icon_path, + func + ) # Help menu self.add_menu_action( @@ -205,16 +222,16 @@ class MainWindow(QtWidgets.QMainWindow): Save current window size and state by trader path and setting name. """ settings = QtCore.QSettings(self.window_title, name) - settings.setValue('state', self.saveState()) - settings.setValue('geometry', self.saveGeometry()) + settings.setValue("state", self.saveState()) + settings.setValue("geometry", self.saveGeometry()) def load_window_setting(self, name: str): """ Load previous window size and state by trader path and setting name. """ settings = QtCore.QSettings(self.window_title, name) - state = settings.value('state') - geometry = settings.value('geometry') + state = settings.value("state") + geometry = settings.value("geometry") if isinstance(state, QtCore.QByteArray): self.restoreState(state) diff --git a/vnpy/trader/utility.py b/vnpy/trader/utility.py index e8a176aa..3c61fd04 100644 --- a/vnpy/trader/utility.py +++ b/vnpy/trader/utility.py @@ -19,7 +19,7 @@ class Singleton(type): def __call__(cls, *args, **kwargs): """""" if cls not in cls._instances: - cls._instances[cls] = super(VtSingleton, + cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)