├── vnpy_scripttrader
├── ui
│ ├── __init__.py
│ ├── script.ico
│ └── widget.py
├── cli.py
├── __init__.py
└── engine.py
├── .github
├── PULL_REQUEST_TEMPLATE.md
├── SUPPORT.md
├── CODE_OF_CONDUCT.md
├── ISSUE_TEMPLATE.md
└── workflows
│ └── pythonapp.yml
├── CHANGELOG.md
├── script
└── run.py
├── README.md
├── LICENSE
├── .gitignore
└── pyproject.toml
/vnpy_scripttrader/ui/__init__.py:
--------------------------------------------------------------------------------
1 | from .widget import ScriptManager
2 |
3 |
4 | __all__ = ["ScriptManager"]
5 |
--------------------------------------------------------------------------------
/vnpy_scripttrader/ui/script.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vnpy/vnpy_scripttrader/HEAD/vnpy_scripttrader/ui/script.ico
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | 建议每次发起的PR内容尽可能精简,复杂的修改请拆分为多次PR,便于管理合并。
2 |
3 | ## 改进内容
4 |
5 | 1.
6 | 2.
7 | 3.
8 |
9 | ## 相关的Issue号(如有)
10 |
11 | Close #
--------------------------------------------------------------------------------
/.github/SUPPORT.md:
--------------------------------------------------------------------------------
1 | # 获取帮助
2 |
3 | 在开发和使用VeighNa项目的过程中遇到问题时,获取帮助的渠道包括:
4 |
5 | * Github Issues:[Issues页面](https://github.com/vnpy/vnpy/issues)
6 | * 官方QQ群: 262656087
7 | * 项目论坛:[VeighNa量化社区](http://www.vnpy.com/forum)
8 | * 项目邮箱: vn.py@foxmail.com
9 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 1.1.1版本
2 |
3 | 1. 注册模块日志输出到日志引擎
4 |
5 | # 1.1.0版本
6 |
7 | 1. vnpy框架4.0版本升级适配
8 |
9 | # 1.0.3版本
10 |
11 | 1. 替换所有的Qt信号pyqtSignal为Signal
12 |
13 | # 1.0.2版本
14 |
15 | 1. 增加基于vt_symbol和direction的持仓查询函数
16 |
17 | # 1.0.1版本
18 |
19 | 1. 将模块的图标文件信息,改为完整路径字符串
20 |
--------------------------------------------------------------------------------
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # 行为准则
2 |
3 | 这是一份VeighNa项目社区的行为准则,也是项目作者自己在刚入行量化金融行业时对于理想中的社区的期望:
4 |
5 | * 为交易员而生:作为一款从金融机构量化业务中诞生的交易系统开发框架,设计上都优先满足机构专业交易员的使用习惯,而不是其他用户(散户、爱好者、技术人员等)
6 |
7 | * 对新用户友好,保持耐心:大部分人在接触新东西的时候都是磕磕碰碰、有很多的问题,请记住此时别人对你伸出的援助之手,并把它传递给未来需要的人
8 |
9 | * 尊重他人,慎重言行:礼貌文明的交流方式除了能得到别人同样的回应,更能减少不必要的摩擦,保证高效的交流
10 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## 环境
2 |
3 | * 操作系统: 如Windows 11或者Ubuntu 22.04
4 | * Python版本: 如VeighNa Studio-4.0.0
5 | * VeighNa版本: 如v4.0.0发行版或者dev branch 20250320(下载日期)
6 |
7 | ## Issue类型
8 | 三选一:Bug/Enhancement/Question
9 |
10 | ## 预期程序行为
11 |
12 |
13 | ## 实际程序行为
14 |
15 |
16 | ## 重现步骤
17 |
18 | 针对Bug类型Issue,请提供具体重现步骤以及报错截图
19 |
20 |
--------------------------------------------------------------------------------
/script/run.py:
--------------------------------------------------------------------------------
1 | from vnpy.event import EventEngine
2 | from vnpy.trader.engine import MainEngine
3 | from vnpy.trader.ui import MainWindow, create_qapp
4 |
5 | from vnpy_ctp import CtpGateway
6 | from vnpy_scripttrader import ScriptTraderApp
7 |
8 |
9 | def main() -> None:
10 | """Start Trader"""
11 | qapp = create_qapp()
12 |
13 | event_engine = EventEngine()
14 | main_engine = MainEngine(event_engine)
15 |
16 | main_engine.add_gateway(CtpGateway)
17 | main_engine.add_app(ScriptTraderApp)
18 |
19 | main_window = MainWindow(main_engine, event_engine)
20 | main_window.showMaximized()
21 |
22 | qapp.exec()
23 |
24 |
25 | if __name__ == "__main__":
26 | main()
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # VeighNa框架的脚本交易模块
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | ## 说明
15 |
16 | ScriptTrader是用于交易脚本执行的功能模块,和其他策略模块最大的区别在于其基于【时间驱动】的【同步逻辑】,也支持在命令行(Jupyter Notebook)中以REPL指令形式的进行交易操作,该模块没有回测功能。
17 |
18 | ## 安装
19 |
20 | 安装环境推荐基于4.0.0版本以上的【[**VeighNa Studio**](https://www.vnpy.com)】。
21 |
22 | 直接使用pip命令:
23 |
24 | ```
25 | pip install vnpy_scripttrader
26 | ```
27 |
28 |
29 | 或者下载源代码后,解压后在cmd中运行:
30 |
31 | ```
32 | pip install .
33 | ```
34 |
--------------------------------------------------------------------------------
/vnpy_scripttrader/cli.py:
--------------------------------------------------------------------------------
1 | from collections.abc import Sequence
2 |
3 | from vnpy.event import EventEngine, Event
4 | from vnpy.trader.engine import MainEngine
5 | from vnpy.trader.gateway import BaseGateway
6 | from vnpy.trader.event import EVENT_LOG
7 | from vnpy.trader.object import LogData
8 |
9 | from .engine import ScriptEngine, BaseEngine
10 |
11 |
12 | def process_log_event(event: Event) -> None:
13 | """打印日志输出"""
14 | log: LogData = event.data
15 | print(f"{log.time}\t{log.msg}")
16 |
17 |
18 | def init_cli_trading(gateways: Sequence[type[BaseGateway]]) -> BaseEngine:
19 | """初始化命令行交易"""
20 | event_engine: EventEngine = EventEngine()
21 | event_engine.register(EVENT_LOG, process_log_event)
22 |
23 | main_engine: MainEngine = MainEngine(event_engine)
24 | for gateway in gateways:
25 | main_engine.add_gateway(gateway)
26 |
27 | script_engine: ScriptEngine = main_engine.add_engine(ScriptEngine)
28 |
29 | return script_engine
30 |
--------------------------------------------------------------------------------
/.github/workflows/pythonapp.yml:
--------------------------------------------------------------------------------
1 | name: Python application
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: windows-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v1
12 | - name: Set up Python 3.13
13 | uses: actions/setup-python@v1
14 | with:
15 | python-version: '3.13'
16 | - name: Install dependencies
17 | run: |
18 | python -m pip install --upgrade pip
19 | pip install ta-lib==0.6.4 --index=https://pypi.vnpy.com
20 | pip install vnpy ruff mypy uv
21 | - name: Lint with ruff
22 | run: |
23 | # Run ruff linter based on pyproject.toml configuration
24 | ruff check .
25 | - name: Type check with mypy
26 | run: |
27 | # Run mypy type checking based on pyproject.toml configuration
28 | mypy vnpy_scripttrader
29 | - name: Build packages with uv
30 | run: |
31 | # Build source distribution and wheel distribution
32 | uv build
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015-present, Xiaoyou Chen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/vnpy_scripttrader/__init__.py:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 | #
3 | # Copyright (c) 2015-present, Xiaoyou Chen
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in all
13 | # copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 |
23 |
24 | from pathlib import Path
25 |
26 | from vnpy.trader.app import BaseApp
27 |
28 | from .engine import ScriptEngine, APP_NAME
29 | from .cli import init_cli_trading
30 |
31 |
32 | __all__ = [
33 | "APP_NAME",
34 | "ScriptEngine",
35 | "ScriptTraderApp",
36 | "init_cli_trading",
37 | ]
38 |
39 |
40 | __version__ = "1.1.1"
41 |
42 |
43 | class ScriptTraderApp(BaseApp):
44 | """"""
45 |
46 | app_name: str = APP_NAME
47 | app_module: str = __module__
48 | app_path: Path = Path(__file__).parent
49 | display_name: str = "脚本策略"
50 | engine_class: type[ScriptEngine] = ScriptEngine
51 | widget_name: str = "ScriptManager"
52 | icon_name: str = str(app_path.joinpath("ui", "script.ico"))
53 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "vnpy_scripttrader"
3 | dynamic = ["version"]
4 | description = "Script trading application for VeighNa quant trading framework."
5 | readme = "README.md"
6 | license = {text = "MIT"}
7 | authors = [{name = "Xiaoyou Chen", email = "xiaoyou.chen@mail.vnpy.com"}]
8 | classifiers = [
9 | "Development Status :: 5 - Production/Stable",
10 | "License :: OSI Approved :: MIT License",
11 | "Operating System :: OS Independent",
12 | "Programming Language :: Python :: 3",
13 | "Programming Language :: Python :: 3.10",
14 | "Programming Language :: Python :: 3.11",
15 | "Programming Language :: Python :: 3.12",
16 | "Programming Language :: Python :: 3.13",
17 | "Topic :: Office/Business :: Financial :: Investment",
18 | "Programming Language :: Python :: Implementation :: CPython",
19 | "Natural Language :: Chinese (Simplified)",
20 | "Typing :: Typed",
21 | ]
22 | requires-python = ">=3.10"
23 | dependencies = [
24 | "vnpy>=4.0.0"
25 | ]
26 | keywords = ["quant", "quantitative", "investment", "trading", "algotrading"]
27 |
28 | [project.urls]
29 | "Homepage" = "https://www.vnpy.com"
30 | "Documentation" = "https://www.vnpy.com/docs"
31 | "Changes" = "https://github.com/vnpy/vnpy_scripttrader/blob/master/CHANGELOG.md"
32 | "Source" = "https://github.com/vnpy/vnpy_scripttrader/"
33 | "Forum" = "https://www.vnpy.com/forum"
34 |
35 | [build-system]
36 | requires = ["hatchling>=1.27.0"]
37 | build-backend = "hatchling.build"
38 |
39 | [tool.hatch.version]
40 | path = "vnpy_scripttrader/__init__.py"
41 | pattern = "__version__ = ['\"](?P[^'\"]+)['\"]"
42 |
43 | [tool.hatch.build.targets.wheel]
44 | packages = ["vnpy_scripttrader"]
45 | include-package-data = true
46 |
47 | [tool.hatch.build.targets.sdist]
48 | include = ["vnpy_scripttrader*"]
49 |
50 | [tool.ruff]
51 | target-version = "py310"
52 | output-format = "full"
53 |
54 | [tool.ruff.lint]
55 | select = [
56 | "B", # flake8-bugbear
57 | "E", # pycodestyle error
58 | "F", # pyflakes
59 | "UP", # pyupgrade
60 | "W", # pycodestyle warning
61 | ]
62 | ignore = ["E501"]
63 |
64 | [tool.mypy]
65 | python_version = "3.10"
66 | warn_return_any = true
67 | warn_unused_configs = true
68 | disallow_untyped_defs = true
69 | disallow_incomplete_defs = true
70 | check_untyped_defs = true
71 | disallow_untyped_decorators = true
72 | no_implicit_optional = true
73 | strict_optional = true
74 | warn_redundant_casts = true
75 | warn_unused_ignores = true
76 | warn_no_return = true
77 | ignore_missing_imports = true
78 |
--------------------------------------------------------------------------------
/vnpy_scripttrader/ui/widget.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | from vnpy.event import EventEngine, Event
4 | from vnpy.trader.engine import MainEngine
5 | from vnpy.trader.ui import QtWidgets, QtCore
6 | from vnpy.trader.object import LogData
7 | from ..engine import APP_NAME, EVENT_SCRIPT_LOG, BaseEngine
8 |
9 |
10 | class ScriptManager(QtWidgets.QWidget):
11 | """"""
12 | signal_log: QtCore.Signal = QtCore.Signal(Event)
13 |
14 | def __init__(self, main_engine: MainEngine, event_engine: EventEngine) -> None:
15 | """"""
16 | super().__init__()
17 |
18 | self.main_engine: MainEngine = main_engine
19 | self.event_engine: EventEngine = event_engine
20 |
21 | self.script_engine: BaseEngine = main_engine.get_engine(APP_NAME)
22 |
23 | self.script_path: str = ""
24 |
25 | self.init_ui()
26 | self.register_event()
27 |
28 | self.script_engine.init()
29 |
30 | def init_ui(self) -> None:
31 | """"""
32 | self.setWindowTitle("脚本策略")
33 |
34 | start_button: QtWidgets.QPushButton = QtWidgets.QPushButton("启动")
35 | start_button.clicked.connect(self.start_script)
36 |
37 | stop_button: QtWidgets.QPushButton = QtWidgets.QPushButton("停止")
38 | stop_button.clicked.connect(self.stop_script)
39 |
40 | select_button: QtWidgets.QPushButton = QtWidgets.QPushButton("打开")
41 | select_button.clicked.connect(self.select_script)
42 |
43 | self.strategy_line: QtWidgets.QLineEdit = QtWidgets.QLineEdit()
44 |
45 | self.log_monitor: QtWidgets.QTextEdit = QtWidgets.QTextEdit()
46 | self.log_monitor.setReadOnly(True)
47 |
48 | clear_button: QtWidgets.QPushButton = QtWidgets.QPushButton("清空")
49 | clear_button.clicked.connect(self.log_monitor.clear)
50 |
51 | hbox: QtWidgets.QHBoxLayout = QtWidgets.QHBoxLayout()
52 | hbox.addWidget(self.strategy_line)
53 | hbox.addWidget(select_button)
54 | hbox.addWidget(start_button)
55 | hbox.addWidget(stop_button)
56 | hbox.addStretch()
57 | hbox.addWidget(clear_button)
58 |
59 | vbox: QtWidgets.QVBoxLayout = QtWidgets.QVBoxLayout()
60 | vbox.addLayout(hbox)
61 | vbox.addWidget(self.log_monitor)
62 |
63 | self.setLayout(vbox)
64 |
65 | def register_event(self) -> None:
66 | """"""
67 | self.signal_log.connect(self.process_log_event)
68 |
69 | self.event_engine.register(EVENT_SCRIPT_LOG, self.signal_log.emit)
70 |
71 | def show(self) -> None:
72 | """"""
73 | self.showMaximized()
74 |
75 | def process_log_event(self, event: Event) -> None:
76 | """"""
77 | log: LogData = event.data
78 | msg: str = f"{log.time}\t{log.msg}"
79 | self.log_monitor.append(msg)
80 |
81 | def start_script(self) -> None:
82 | """"""
83 | if self.script_path:
84 | self.script_engine.start_strategy(self.script_path)
85 |
86 | def stop_script(self) -> None:
87 | """"""
88 | self.script_engine.stop_strategy()
89 |
90 | def select_script(self) -> None:
91 | """"""
92 | cwd: str = str(Path.cwd())
93 |
94 | path, type_ = QtWidgets.QFileDialog.getOpenFileName(
95 | self,
96 | "载入策略脚本",
97 | cwd,
98 | "Python File(*.py)"
99 | )
100 |
101 | if path:
102 | self.script_path = path
103 | self.strategy_line.setText(path)
104 |
--------------------------------------------------------------------------------
/vnpy_scripttrader/engine.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import importlib
3 | import traceback
4 | from types import ModuleType
5 | from typing import Any
6 | from collections.abc import Callable
7 | from collections.abc import Sequence
8 | from pathlib import Path
9 | from datetime import datetime
10 | from threading import Thread
11 |
12 | from pandas import DataFrame
13 |
14 | from vnpy.event import Event, EventEngine
15 | from vnpy.trader.engine import BaseEngine, MainEngine, LogEngine
16 | from vnpy.trader.constant import Direction, Offset, OrderType, Interval
17 | from vnpy.trader.object import (
18 | BaseData,
19 | OrderRequest,
20 | HistoryRequest,
21 | SubscribeRequest,
22 | TickData,
23 | OrderData,
24 | TradeData,
25 | PositionData,
26 | AccountData,
27 | ContractData,
28 | LogData,
29 | BarData,
30 | CancelRequest
31 | )
32 | from vnpy.trader.datafeed import BaseDatafeed, get_datafeed
33 |
34 |
35 | APP_NAME = "ScriptTrader"
36 |
37 | EVENT_SCRIPT_LOG = "eScriptLog"
38 |
39 |
40 | class ScriptEngine(BaseEngine):
41 | """"""
42 | setting_filename: str = "script_trader_setting.json"
43 |
44 | def __init__(self, main_engine: MainEngine, event_engine: EventEngine) -> None:
45 | """"""
46 | super().__init__(main_engine, event_engine, APP_NAME)
47 |
48 | self.strategy_active: bool = False
49 | self.strategy_thread: Thread | None = None
50 |
51 | self.datafeed: BaseDatafeed = get_datafeed()
52 |
53 | log_engine: LogEngine = self.main_engine.get_engine("log")
54 | log_engine.register_log(EVENT_SCRIPT_LOG)
55 |
56 | def init(self) -> None:
57 | """启动策略引擎"""
58 | result: bool = self.datafeed.init()
59 | if result:
60 | self.write_log("数据服务初始化成功")
61 |
62 | def start_strategy(self, script_path: str) -> None:
63 | """运行策略线程中的策略方法"""
64 | if self.strategy_active:
65 | return
66 | self.strategy_active = True
67 |
68 | self.strategy_thread = Thread(
69 | target=self.run_strategy, args=(script_path,))
70 | self.strategy_thread.start()
71 |
72 | self.write_log("策略交易脚本启动")
73 |
74 | def run_strategy(self, script_path: str) -> None:
75 | """加载策略脚本并调用run函数"""
76 | path: Path = Path(script_path)
77 | sys.path.append(str(path.parent))
78 |
79 | script_name: str = path.parts[-1]
80 | module_name: str = script_name.replace(".py", "")
81 |
82 | try:
83 | module: ModuleType = importlib.import_module(module_name)
84 | importlib.reload(module)
85 | module.run(self)
86 | except Exception:
87 | msg: str = f"触发异常已停止\n{traceback.format_exc()}"
88 | self.write_log(msg)
89 |
90 | def stop_strategy(self) -> None:
91 | """停止运行中的策略"""
92 | if not self.strategy_active:
93 | return
94 | self.strategy_active = False
95 |
96 | if self.strategy_thread:
97 | self.strategy_thread.join()
98 | self.strategy_thread = None
99 |
100 | self.write_log("策略交易脚本停止")
101 |
102 | def connect_gateway(self, setting: dict, gateway_name: str) -> None:
103 | """"""
104 | self.main_engine.connect(setting, gateway_name)
105 |
106 | def send_order(
107 | self,
108 | vt_symbol: str,
109 | price: float,
110 | volume: float,
111 | direction: Direction,
112 | offset: Offset,
113 | order_type: OrderType
114 | ) -> str:
115 | """"""
116 | contract: ContractData | None = self.get_contract(vt_symbol)
117 | if not contract:
118 | return ""
119 |
120 | req: OrderRequest = OrderRequest(
121 | symbol=contract.symbol,
122 | exchange=contract.exchange,
123 | direction=direction,
124 | type=order_type,
125 | volume=volume,
126 | price=price,
127 | offset=offset,
128 | reference=APP_NAME
129 | )
130 |
131 | vt_orderid: str = self.main_engine.send_order(req, contract.gateway_name)
132 | return vt_orderid
133 |
134 | def subscribe(self, vt_symbols: Sequence[str]) -> None:
135 | """"""
136 | for vt_symbol in vt_symbols:
137 | contract: ContractData | None = self.main_engine.get_contract(vt_symbol)
138 | if contract:
139 | req: SubscribeRequest = SubscribeRequest(
140 | symbol=contract.symbol,
141 | exchange=contract.exchange
142 | )
143 | self.main_engine.subscribe(req, contract.gateway_name)
144 |
145 | def buy(
146 | self,
147 | vt_symbol: str,
148 | price: float,
149 | volume: float,
150 | order_type: OrderType = OrderType.LIMIT
151 | ) -> str:
152 | """"""
153 | return self.send_order(vt_symbol, price, volume, Direction.LONG, Offset.OPEN, order_type)
154 |
155 | def sell(
156 | self,
157 | vt_symbol: str,
158 | price: float,
159 | volume: float,
160 | order_type: OrderType = OrderType.LIMIT
161 | ) -> str:
162 | """"""
163 | return self.send_order(vt_symbol, price, volume, Direction.SHORT, Offset.CLOSE, order_type)
164 |
165 | def short(
166 | self,
167 | vt_symbol: str,
168 | price: float,
169 | volume: float,
170 | order_type: OrderType = OrderType.LIMIT
171 | ) -> str:
172 | """"""
173 | return self.send_order(vt_symbol, price, volume, Direction.SHORT, Offset.OPEN, order_type)
174 |
175 | def cover(
176 | self,
177 | vt_symbol: str,
178 | price: float,
179 | volume: float,
180 | order_type: OrderType = OrderType.LIMIT
181 | ) -> str:
182 | """"""
183 | return self.send_order(vt_symbol, price, volume, Direction.LONG, Offset.CLOSE, order_type)
184 |
185 | def cancel_order(self, vt_orderid: str) -> None:
186 | """"""
187 | order: OrderData | None = self.get_order(vt_orderid)
188 | if not order:
189 | return
190 |
191 | req: CancelRequest = order.create_cancel_request()
192 | self.main_engine.cancel_order(req, order.gateway_name)
193 |
194 | def get_tick(self, vt_symbol: str, use_df: bool = False) -> TickData | None:
195 | """"""
196 | return get_data(self.main_engine.get_tick, arg=vt_symbol, use_df=use_df)
197 |
198 | def get_ticks(self, vt_symbols: Sequence[str], use_df: bool = False) -> Sequence[TickData] | DataFrame | None:
199 | """"""
200 | ticks: list = []
201 | for vt_symbol in vt_symbols:
202 | tick: TickData | None = self.main_engine.get_tick(vt_symbol)
203 | ticks.append(tick)
204 |
205 | if not use_df:
206 | return ticks
207 | else:
208 | return to_df(ticks)
209 |
210 | def get_order(self, vt_orderid: str, use_df: bool = False) -> OrderData | None:
211 | """"""
212 | return get_data(self.main_engine.get_order, arg=vt_orderid, use_df=use_df)
213 |
214 | def get_orders(self, vt_orderids: Sequence[str], use_df: bool = False) -> Sequence[OrderData] | DataFrame | None:
215 | """"""
216 | orders: list = []
217 | for vt_orderid in vt_orderids:
218 | order: OrderData | None = self.main_engine.get_order(vt_orderid)
219 | orders.append(order)
220 |
221 | if not use_df:
222 | return orders
223 | else:
224 | return to_df(orders)
225 |
226 | def get_trades(self, vt_orderid: str, use_df: bool = False) -> Sequence[TradeData] | DataFrame | None:
227 | """"""
228 | trades: list = []
229 | all_trades: list[TradeData] = self.main_engine.get_all_trades()
230 |
231 | for trade in all_trades:
232 | if trade.vt_orderid == vt_orderid:
233 | trades.append(trade)
234 |
235 | if not use_df:
236 | return trades
237 | else:
238 | return to_df(trades)
239 |
240 | def get_all_active_orders(self, use_df: bool = False) -> Sequence[OrderData] | DataFrame | None:
241 | """"""
242 | return get_data(self.main_engine.get_all_active_orders, use_df=use_df)
243 |
244 | def get_contract(self, vt_symbol: str, use_df: bool = False) -> ContractData | None:
245 | """"""
246 | return get_data(self.main_engine.get_contract, arg=vt_symbol, use_df=use_df)
247 |
248 | def get_all_contracts(self, use_df: bool = False) -> Sequence[ContractData] | DataFrame | None:
249 | """"""
250 | return get_data(self.main_engine.get_all_contracts, use_df=use_df)
251 |
252 | def get_account(self, vt_accountid: str, use_df: bool = False) -> AccountData | None:
253 | """"""
254 | return get_data(self.main_engine.get_account, arg=vt_accountid, use_df=use_df)
255 |
256 | def get_all_accounts(self, use_df: bool = False) -> Sequence[AccountData] | DataFrame | None:
257 | """"""
258 | return get_data(self.main_engine.get_all_accounts, use_df=use_df)
259 |
260 | def get_position(self, vt_positionid: str, use_df: bool = False) -> PositionData | None:
261 | """"""
262 | return get_data(self.main_engine.get_position, arg=vt_positionid, use_df=use_df)
263 |
264 | def get_position_by_symbol(self, vt_symbol: str, direction: Direction, use_df: bool = False) -> PositionData | None:
265 | """"""
266 | contract: ContractData = self.main_engine.get_contract(vt_symbol)
267 | if not contract:
268 | return None
269 |
270 | vt_positionid: str = f"{contract.gateway_name}.{contract.vt_symbol}.{direction.value}"
271 | return get_data(self.main_engine.get_position, arg=vt_positionid, use_df=use_df)
272 |
273 | def get_all_positions(self, use_df: bool = False) -> Sequence[PositionData] | DataFrame | None:
274 | """"""
275 | return get_data(self.main_engine.get_all_positions, use_df=use_df)
276 |
277 | def get_bars(
278 | self,
279 | vt_symbol: str,
280 | start_date: str,
281 | interval: Interval,
282 | use_df: bool = False
283 | ) -> Sequence[BarData]:
284 | """"""
285 | contract: ContractData | None = self.main_engine.get_contract(vt_symbol)
286 | if not contract:
287 | return []
288 |
289 | start: datetime = datetime.strptime(start_date, "%Y%m%d")
290 | end: datetime = datetime.now()
291 |
292 | req: HistoryRequest = HistoryRequest(
293 | symbol=contract.symbol,
294 | exchange=contract.exchange,
295 | start=start,
296 | end=end,
297 | interval=interval
298 | )
299 |
300 | bars: Sequence[BarData] | DataFrame = get_data(self.datafeed.query_bar_history, arg=req, use_df=use_df)
301 | return bars
302 |
303 | def write_log(self, msg: str) -> None:
304 | """"""
305 | log: LogData = LogData(msg=msg, gateway_name=APP_NAME)
306 | print(f"{log.time}\t{log.msg}")
307 |
308 | event: Event = Event(EVENT_SCRIPT_LOG, log)
309 | self.event_engine.put(event)
310 |
311 | def send_email(self, msg: str) -> None:
312 | """"""
313 | subject: str = "脚本策略引擎通知"
314 | self.main_engine.send_email(subject, msg)
315 |
316 |
317 | def to_df(data_list: Sequence[BaseData]) -> DataFrame | None:
318 | """"""
319 | if not data_list:
320 | return None
321 |
322 | dict_list: list = [data.__dict__ for data in data_list if data]
323 | return DataFrame(dict_list)
324 |
325 |
326 | def get_data(func: Callable, arg: Any = None, use_df: bool = False) -> Any:
327 | """"""
328 | if not arg:
329 | data = func()
330 | else:
331 | data = func(arg)
332 |
333 | if not use_df:
334 | return data
335 | elif data is None:
336 | return data
337 | else:
338 | if not isinstance(data, list):
339 | data = [data]
340 | return to_df(data)
341 |
--------------------------------------------------------------------------------