├── .github
├── CODE_OF_CONDUCT.md
├── ISSUE_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE.md
├── SUPPORT.md
└── workflows
│ └── pythonapp.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── pyproject.toml
└── vnpy_websocket
├── __init__.py
└── websocket_client.py
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.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.3 --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_websocket
29 | - name: Build packages with uv
30 | run: |
31 | # Build source distribution and wheel distribution
32 | uv build --sdist
33 |
--------------------------------------------------------------------------------
/.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 | .vscode/settings.json
131 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 1.1.1版本
2 |
3 | 1. 基于mypy完善代码类型提示
4 |
5 | # 1.1.0版本
6 |
7 | 1. 替换aiohttp异步通讯模式,改为使用websocket-client多线程模式
8 | 2. 替换使用pyproject.toml配置
9 | 3. ruff和mypy代码质量优化
10 |
11 | # 1.0.6版本
12 |
13 | 1. 在Windows系统上必须使用Selector事件循环,否则可能导致程序崩溃
14 | 2. 客户端停止时,确保关闭所有会话
15 | 3. 等待异步关闭任务完成后,才停止事件循环
16 | 4. 增加默认的60秒超时自动断线重连
17 |
18 | # 1.0.5版本
19 |
20 | 1. 修复aiohttp的代理参数proxy传空时必须为None的问题
21 |
22 | # 1.0.4版本
23 |
24 | 1. 对Python 3.10后asyncio的支持修改
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # VeighNa框架的Websocket API客户端
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | ## 说明
15 |
16 | 基于websocket-client开发的多线程Websocket API客户端,用于开发高性能的Websocket交易接口。
17 |
18 | ## 安装
19 |
20 | 安装环境推荐基于4.0.0版本以上的【[**VeighNa Studio**](https://www.vnpy.com)】。
21 |
22 | 直接使用pip命令:
23 |
24 | ```
25 | pip install vnpy_websocket
26 | ```
27 |
28 | 下载解压后在cmd中运行
29 |
30 | ```
31 | pip install .
32 | ```
33 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "vnpy_websocket"
3 | dynamic = ["version"]
4 | description = "Websocket API client 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 | "Programming Language :: Python :: Implementation :: CPython",
18 | "Topic :: Office/Business :: Financial :: Investment",
19 | "Natural Language :: Chinese (Simplified)",
20 | "Typing :: Typed",
21 | ]
22 | requires-python = ">=3.10"
23 | dependencies = [
24 | "websocket-client>=1.8.0",
25 | ]
26 |
27 | keywords = ["quant", "quantitative", "investment", "trading", "algotrading"]
28 |
29 | [project.urls]
30 | "Homepage" = "https://www.vnpy.com"
31 | "Documentation" = "https://www.vnpy.com/docs"
32 | "Changes" = "https://github.com/vnpy/vnpy_websocket/blob/master/CHANGELOG.md"
33 | "Source" = "https://github.com/vnpy/vnpy_websocket"
34 | "Forum" = "https://www.vnpy.com/forum"
35 |
36 | [build-system]
37 | requires = ["hatchling>=1.27.0"]
38 | build-backend = "hatchling.build"
39 |
40 | [tool.hatch.version]
41 | path = "vnpy_websocket/__init__.py"
42 | pattern = "__version__ = ['\"](?P[^'\"]+)['\"]"
43 |
44 | [tool.hatch.build.targets.wheel]
45 | packages = ["vnpy_websocket"]
46 | include-package-data = true
47 |
48 | [tool.hatch.build.targets.sdist]
49 | include = ["vnpy_websocket*"]
50 |
51 | [tool.ruff]
52 | target-version = "py310"
53 | output-format = "full"
54 |
55 | [tool.ruff.lint]
56 | select = [
57 | "B", # flake8-bugbear
58 | "E", # pycodestyle error
59 | "F", # pyflakes
60 | "UP", # pyupgrade
61 | "W", # pycodestyle warning
62 | ]
63 | ignore = ["E501"]
64 |
65 | [tool.mypy]
66 | python_version = "3.10"
67 | warn_return_any = true
68 | warn_unused_configs = true
69 | disallow_untyped_defs = true
70 | disallow_incomplete_defs = true
71 | check_untyped_defs = true
72 | disallow_untyped_decorators = true
73 | no_implicit_optional = true
74 | strict_optional = true
75 | warn_redundant_casts = true
76 | warn_unused_ignores = true
77 | warn_no_return = true
--------------------------------------------------------------------------------
/vnpy_websocket/__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 | from .websocket_client import WebsocketClient
24 |
25 |
26 | __all__ = ["WebsocketClient"]
27 |
28 |
29 | __version__ = "1.1.1"
30 |
--------------------------------------------------------------------------------
/vnpy_websocket/websocket_client.py:
--------------------------------------------------------------------------------
1 | import json
2 | import ssl
3 | import traceback
4 | from threading import Thread
5 |
6 | import websocket
7 |
8 |
9 | class WebsocketClient:
10 | """
11 | Websocket API
12 |
13 | After creating the client object, use start() to run worker thread.
14 | The worker thread connects websocket automatically.
15 |
16 | Use stop to stop threads and disconnect websocket before destroying the client
17 | object (especially when exiting the programme).
18 |
19 | Default serialization format is json.
20 |
21 | Callbacks to overrides:
22 | * on_connected
23 | * on_disconnected
24 | * on_packet
25 | * on_error
26 |
27 | If you want to send anything other than JSON, override send_packet.
28 | """
29 |
30 | def __init__(self) -> None:
31 | """Constructor"""
32 | self.active: bool = False
33 | self.host: str = ""
34 |
35 | self.wsapp: websocket.WebSocketApp | None = None
36 | self.thread: Thread | None = None
37 |
38 | self.proxy_host: str = ""
39 | self.proxy_port: int = 0
40 | self.header: dict | None = None
41 | self.ping_interval: int = 0
42 | self.receive_timeout: int = 0
43 |
44 | self.trace: bool = False
45 |
46 | def init(
47 | self,
48 | host: str,
49 | proxy_host: str = "",
50 | proxy_port: int = 0,
51 | ping_interval: int = 10,
52 | receive_timeout: int = 60,
53 | header: dict | None = None,
54 | trace: bool = False
55 | ) -> None:
56 | """
57 | :param host:
58 | :param proxy_host:
59 | :param proxy_port:
60 | :param header:
61 | :param ping_interval: unit: seconds, type: int
62 | """
63 | self.host = host
64 | self.ping_interval = ping_interval # seconds
65 | self.receive_timeout = receive_timeout
66 |
67 | if header:
68 | self.header = header
69 |
70 | if proxy_host and proxy_port:
71 | self.proxy_host = proxy_host
72 | self.proxy_port = proxy_port
73 |
74 | websocket.enableTrace(trace)
75 | websocket.setdefaulttimeout(receive_timeout)
76 |
77 | def start(self) -> None:
78 | """
79 | Start the client and on_connected function is called after webscoket
80 | is connected succesfully.
81 |
82 | Please don't send packet untill on_connected fucntion is called.
83 | """
84 | self.active = True
85 | self.thread = Thread(target=self.run)
86 | self.thread.start()
87 |
88 | def stop(self) -> None:
89 | """
90 | Stop the client.
91 | """
92 | if not self.active:
93 | return
94 | self.active = False
95 |
96 | if self.wsapp:
97 | self.wsapp.close()
98 |
99 | def join(self) -> None:
100 | """
101 | Wait till all threads finish.
102 |
103 | This function cannot be called from worker thread or callback function.
104 | """
105 | if self.thread:
106 | self.thread.join()
107 |
108 | def send_packet(self, packet: dict) -> None:
109 | """
110 | Send a packet (dict data) to server
111 |
112 | override this if you want to send non-json packet
113 | """
114 | text: str = json.dumps(packet)
115 | if self.wsapp:
116 | self.wsapp.send(text)
117 |
118 | def run(self) -> None:
119 | """
120 | Keep running till stop is called.
121 | """
122 | def on_open(wsapp: websocket.WebSocket) -> None:
123 | self.on_connected()
124 |
125 | def on_close(wsapp: websocket.WebSocket, status_code: int, msg: str) -> None:
126 | self.on_disconnected(status_code, msg)
127 |
128 | def on_error(wsapp: websocket.WebSocket, e: Exception) -> None:
129 | self.on_error(e)
130 |
131 | def on_message(wsapp: websocket.WebSocket, message: str) -> None:
132 | self.on_message(message)
133 |
134 | self.wsapp = websocket.WebSocketApp(
135 | url=self.host,
136 | header=self.header,
137 | on_open=on_open,
138 | on_close=on_close,
139 | on_error=on_error,
140 | on_message=on_message
141 | )
142 |
143 | proxy_type: str = ""
144 | if self.proxy_host:
145 | proxy_type = "http"
146 |
147 | self.wsapp.run_forever(
148 | sslopt={"cert_reqs": ssl.CERT_NONE},
149 | ping_interval=self.ping_interval,
150 | http_proxy_host=self.proxy_host,
151 | http_proxy_port=self.proxy_port,
152 | proxy_type=proxy_type,
153 | reconnect=1
154 | )
155 |
156 | def on_message(self, message: str) -> None:
157 | """
158 | Callback when weboscket app receives new message
159 | """
160 | self.on_packet(json.loads(message))
161 |
162 | def on_connected(self) -> None:
163 | """
164 | Callback when websocket is connected successfully.
165 | """
166 | pass
167 |
168 | def on_disconnected(self, status_code: int, msg: str) -> None:
169 | """
170 | Callback when websocket connection is closed.
171 | """
172 | pass
173 |
174 | def on_packet(self, packet: dict) -> None:
175 | """
176 | Callback when receiving data from server.
177 | """
178 | pass
179 |
180 | def on_error(self, e: Exception) -> None:
181 | """
182 | Callback when exception raised.
183 | """
184 | try:
185 | print("WebsocketClient on error" + "-" * 10)
186 | print(e)
187 | except Exception:
188 | traceback.print_exc()
189 |
--------------------------------------------------------------------------------