vnpy-xtdata 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vnpy_xtdata-1.0.0/.gitignore +45 -0
- vnpy_xtdata-1.0.0/CHANGELOG.md +0 -0
- vnpy_xtdata-1.0.0/LICENSE +21 -0
- vnpy_xtdata-1.0.0/PKG-INFO +94 -0
- vnpy_xtdata-1.0.0/README.md +63 -0
- vnpy_xtdata-1.0.0/pyproject.toml +68 -0
- vnpy_xtdata-1.0.0/setup.cfg +4 -0
- vnpy_xtdata-1.0.0/setup.py +20 -0
- vnpy_xtdata-1.0.0/vnpy_xtdata/__init__.py +13 -0
- vnpy_xtdata-1.0.0/vnpy_xtdata/_version.py +21 -0
- vnpy_xtdata-1.0.0/vnpy_xtdata/xt_datafeed.py +286 -0
- vnpy_xtdata-1.0.0/vnpy_xtdata/xt_datapub.py +593 -0
- vnpy_xtdata-1.0.0/vnpy_xtdata.egg-info/PKG-INFO +94 -0
- vnpy_xtdata-1.0.0/vnpy_xtdata.egg-info/SOURCES.txt +15 -0
- vnpy_xtdata-1.0.0/vnpy_xtdata.egg-info/dependency_links.txt +1 -0
- vnpy_xtdata-1.0.0/vnpy_xtdata.egg-info/requires.txt +10 -0
- vnpy_xtdata-1.0.0/vnpy_xtdata.egg-info/top_level.txt +3 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
*.pyc
|
|
3
|
+
*.pyo
|
|
4
|
+
*.pyd
|
|
5
|
+
*.egg-info/
|
|
6
|
+
|
|
7
|
+
# Jupyter
|
|
8
|
+
.ipynb_checkpoints
|
|
9
|
+
|
|
10
|
+
# IDE
|
|
11
|
+
.vscode
|
|
12
|
+
.idea
|
|
13
|
+
*.wpr
|
|
14
|
+
*.wpu
|
|
15
|
+
.vs
|
|
16
|
+
x64
|
|
17
|
+
|
|
18
|
+
# Temp
|
|
19
|
+
build
|
|
20
|
+
dist
|
|
21
|
+
*.local
|
|
22
|
+
|
|
23
|
+
# VeighNa
|
|
24
|
+
.vntrader
|
|
25
|
+
|
|
26
|
+
# Visual Studio intermediate files
|
|
27
|
+
*.exp
|
|
28
|
+
*.iobj
|
|
29
|
+
*.ipdb
|
|
30
|
+
*.pdb
|
|
31
|
+
|
|
32
|
+
# Documents
|
|
33
|
+
_build
|
|
34
|
+
_static
|
|
35
|
+
_templates
|
|
36
|
+
|
|
37
|
+
# Misc
|
|
38
|
+
.DS_Store
|
|
39
|
+
*.mo
|
|
40
|
+
|
|
41
|
+
*.log
|
|
42
|
+
# Alpha
|
|
43
|
+
lab/
|
|
44
|
+
|
|
45
|
+
vnpy_xtdata/_version.py
|
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
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.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vnpy_xtdata
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Xtquant datafeed/pub interface.
|
|
5
|
+
Author-email: YQ Tsui <qianyun210603@hotmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: quant,quantitative,investment,trading,algotrading,data
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
15
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
16
|
+
Classifier: Natural Language :: Chinese (Simplified)
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: vnpy>=4.0.0
|
|
22
|
+
Requires-Dist: xtquant>=240920.1.2
|
|
23
|
+
Requires-Dist: filelock>=3
|
|
24
|
+
Requires-Dist: pandas>=2
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: ruff; extra == "dev"
|
|
27
|
+
Requires-Dist: setuptools_scm>=8; extra == "dev"
|
|
28
|
+
Requires-Dist: setuptools>=64; extra == "dev"
|
|
29
|
+
Requires-Dist: wheel; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
<h1 align="center">VeighNa框架的迅投数据服务接口</h1>
|
|
33
|
+
|
|
34
|
+
***
|
|
35
|
+
|
|
36
|
+
<p align="center">
|
|
37
|
+
<img src ="https://img.shields.io/badge/version-1.0.0-blueviolet.svg"/>
|
|
38
|
+
<img src ="https://img.shields.io/badge/platform-windows-yellow.svg"/>
|
|
39
|
+
<img src ="https://img.shields.io/badge/python-3.11|3.12|3.13-blue.svg" />
|
|
40
|
+
<img src ="https://img.shields.io/github/license/vnpy/vnpy.svg?color=orange"/>
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
## 说明
|
|
44
|
+
|
|
45
|
+
基于迅投XtQuant封装开发的实时行情和数据服务接口,支持以下中国金融市场的K线和Tick数据:
|
|
46
|
+
|
|
47
|
+
* 股票、基金、债券、ETF期权:
|
|
48
|
+
* SSE:上海证券交易所
|
|
49
|
+
* SZSE:深圳证券交易所
|
|
50
|
+
* 期货、期货期权:
|
|
51
|
+
* CFFEX:中国金融期货交易所
|
|
52
|
+
* SHFE:上海期货交易所
|
|
53
|
+
* DCE:大连商品交易所
|
|
54
|
+
* CZCE:郑州商品交易所
|
|
55
|
+
* INE:上海国际能源交易中心
|
|
56
|
+
* GFEX:广州期货交易所
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
## 安装
|
|
60
|
+
|
|
61
|
+
安装环境推荐基于4.0.0版本以上的【[**VeighNa Studio**](https://www.vnpy.com/)】。
|
|
62
|
+
|
|
63
|
+
直接使用pip命令:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
pip install vnpy_xtdata
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
或者下载解压后在cmd中运行:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
pip install .
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 使用
|
|
77
|
+
|
|
78
|
+
**Token连接**
|
|
79
|
+
|
|
80
|
+
1. 连接前请先确保xtquant模块可以正常加载(在[投研知识库](http://docs.thinktrader.net/)下载xtquant的安装包,解压后放置xtquant包到自己使用的Python环境的site_packages文件夹下)。
|
|
81
|
+
2. 登录[迅投研服务平台](https://xuntou.net/#/userInfo),在【用户中心】-【个人设置】-【接口TOKEN】处获取Token。
|
|
82
|
+
3. 在VeighNa Trader的【全局配置】处进行数据服务配置:
|
|
83
|
+
* datafeed.name:xt
|
|
84
|
+
* datafeed.username:token
|
|
85
|
+
* datafeed.password:填复制的Token
|
|
86
|
+
|
|
87
|
+
**客户端连接**
|
|
88
|
+
|
|
89
|
+
1. 连接请先登录迅投极速交易终端,同时确保xtquant模块可以正常加载(点击【下载Python库】-【Python库下载】,下载完成后拷贝“Python库路径”下Lib\site-packages文件夹中的xtquant包到自己使用的Python环境的site_packages文件夹下)。
|
|
90
|
+
2. 在Veighna Trader的【全局配置】处进行数据服务配置:
|
|
91
|
+
* datafeed.name:xt
|
|
92
|
+
* datafeed.username:client
|
|
93
|
+
* datafeed.password:留空
|
|
94
|
+
3. 请注意以客户端方式连接时,需要保持迅投客户端的运行。
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<h1 align="center">VeighNa框架的迅投数据服务接口</h1>
|
|
2
|
+
|
|
3
|
+
***
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src ="https://img.shields.io/badge/version-1.0.0-blueviolet.svg"/>
|
|
7
|
+
<img src ="https://img.shields.io/badge/platform-windows-yellow.svg"/>
|
|
8
|
+
<img src ="https://img.shields.io/badge/python-3.11|3.12|3.13-blue.svg" />
|
|
9
|
+
<img src ="https://img.shields.io/github/license/vnpy/vnpy.svg?color=orange"/>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
## 说明
|
|
13
|
+
|
|
14
|
+
基于迅投XtQuant封装开发的实时行情和数据服务接口,支持以下中国金融市场的K线和Tick数据:
|
|
15
|
+
|
|
16
|
+
* 股票、基金、债券、ETF期权:
|
|
17
|
+
* SSE:上海证券交易所
|
|
18
|
+
* SZSE:深圳证券交易所
|
|
19
|
+
* 期货、期货期权:
|
|
20
|
+
* CFFEX:中国金融期货交易所
|
|
21
|
+
* SHFE:上海期货交易所
|
|
22
|
+
* DCE:大连商品交易所
|
|
23
|
+
* CZCE:郑州商品交易所
|
|
24
|
+
* INE:上海国际能源交易中心
|
|
25
|
+
* GFEX:广州期货交易所
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
## 安装
|
|
29
|
+
|
|
30
|
+
安装环境推荐基于4.0.0版本以上的【[**VeighNa Studio**](https://www.vnpy.com/)】。
|
|
31
|
+
|
|
32
|
+
直接使用pip命令:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
pip install vnpy_xtdata
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
或者下载解压后在cmd中运行:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
pip install .
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 使用
|
|
46
|
+
|
|
47
|
+
**Token连接**
|
|
48
|
+
|
|
49
|
+
1. 连接前请先确保xtquant模块可以正常加载(在[投研知识库](http://docs.thinktrader.net/)下载xtquant的安装包,解压后放置xtquant包到自己使用的Python环境的site_packages文件夹下)。
|
|
50
|
+
2. 登录[迅投研服务平台](https://xuntou.net/#/userInfo),在【用户中心】-【个人设置】-【接口TOKEN】处获取Token。
|
|
51
|
+
3. 在VeighNa Trader的【全局配置】处进行数据服务配置:
|
|
52
|
+
* datafeed.name:xt
|
|
53
|
+
* datafeed.username:token
|
|
54
|
+
* datafeed.password:填复制的Token
|
|
55
|
+
|
|
56
|
+
**客户端连接**
|
|
57
|
+
|
|
58
|
+
1. 连接请先登录迅投极速交易终端,同时确保xtquant模块可以正常加载(点击【下载Python库】-【Python库下载】,下载完成后拷贝“Python库路径”下Lib\site-packages文件夹中的xtquant包到自己使用的Python环境的site_packages文件夹下)。
|
|
59
|
+
2. 在Veighna Trader的【全局配置】处进行数据服务配置:
|
|
60
|
+
* datafeed.name:xt
|
|
61
|
+
* datafeed.username:client
|
|
62
|
+
* datafeed.password:留空
|
|
63
|
+
3. 请注意以客户端方式连接时,需要保持迅投客户端的运行。
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "vnpy_xtdata"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "Xtquant datafeed/pub interface."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
authors = [{name = "YQ Tsui", email = "qianyun210603@hotmail.com"}]
|
|
8
|
+
classifiers = [
|
|
9
|
+
"Development Status :: 5 - Production/Stable",
|
|
10
|
+
"Operating System :: Microsoft :: Windows",
|
|
11
|
+
"Programming Language :: Python :: 3",
|
|
12
|
+
"Programming Language :: Python :: 3.11",
|
|
13
|
+
"Programming Language :: Python :: 3.12",
|
|
14
|
+
"Programming Language :: Python :: 3.13",
|
|
15
|
+
"Programming Language :: Python :: Implementation :: CPython",
|
|
16
|
+
"Topic :: Office/Business :: Financial :: Investment",
|
|
17
|
+
"Natural Language :: Chinese (Simplified)",
|
|
18
|
+
"Typing :: Typed",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
requires-python = ">=3.11"
|
|
22
|
+
|
|
23
|
+
dependencies = [
|
|
24
|
+
"vnpy>=4.0.0",
|
|
25
|
+
"xtquant>=240920.1.2",
|
|
26
|
+
"filelock>=3",
|
|
27
|
+
"pandas>=2",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
keywords = ["quant", "quantitative", "investment", "trading", "algotrading", "data"]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = [
|
|
34
|
+
"ruff",
|
|
35
|
+
"setuptools_scm>=8",
|
|
36
|
+
"setuptools>=64",
|
|
37
|
+
"wheel",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[build-system]
|
|
41
|
+
requires = ["setuptools>=64", "wheel", "setuptools_scm>=8"]
|
|
42
|
+
build-backend = "setuptools.build_meta"
|
|
43
|
+
|
|
44
|
+
[tool.setuptools]
|
|
45
|
+
include-package-data = true
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.packages.find]
|
|
48
|
+
exclude = ['script']
|
|
49
|
+
|
|
50
|
+
[tool.setuptools_scm]
|
|
51
|
+
write_to = "vnpy_xtdata/_version.py"
|
|
52
|
+
|
|
53
|
+
[tool.ruff]
|
|
54
|
+
target-version = "py312"
|
|
55
|
+
output-format = "full"
|
|
56
|
+
line-length = 120
|
|
57
|
+
exclude = ["*.ipynb"]
|
|
58
|
+
|
|
59
|
+
[tool.ruff.lint]
|
|
60
|
+
select = [
|
|
61
|
+
"B", # flake8-bugbear
|
|
62
|
+
"E", # pycodestyle error
|
|
63
|
+
"F", # pyflakes
|
|
64
|
+
"UP", # pyupgrade
|
|
65
|
+
"W", # pycodestyle warning
|
|
66
|
+
"PL" # pylint
|
|
67
|
+
]
|
|
68
|
+
ignore = ["E501", "PLR0913", "I001", "PLR2004", "PLR0915", "B027", "PLR5501", "PLR0912"]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from setuptools_scm.version import ScmVersion, guess_next_version
|
|
2
|
+
from setuptools import setup
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def custom_local_scheme(version: ScmVersion):
|
|
6
|
+
if version.exact:
|
|
7
|
+
return ""
|
|
8
|
+
else:
|
|
9
|
+
return "+git." + version.node[1:] if version.node else ""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def custom_version_scheme(version: ScmVersion):
|
|
13
|
+
print(version)
|
|
14
|
+
if version.exact:
|
|
15
|
+
return version.format_with("{tag}")
|
|
16
|
+
else:
|
|
17
|
+
return guess_next_version(version)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
setup(use_scm_version={"version_scheme": custom_version_scheme, "local_scheme": custom_local_scheme})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# @Time : 2025/4/28 19:24
|
|
2
|
+
# @Author : YQ Tsui
|
|
3
|
+
# @File : __init__.py
|
|
4
|
+
# @Purpose :
|
|
5
|
+
|
|
6
|
+
from .xt_datapub import XtMdApi, generate_datetime
|
|
7
|
+
from .xt_datafeed import XtDatafeed as Datafeed
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"XtMdApi",
|
|
11
|
+
"Datafeed",
|
|
12
|
+
"generate_datetime",
|
|
13
|
+
]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
6
|
+
TYPE_CHECKING = False
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
11
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
12
|
+
else:
|
|
13
|
+
VERSION_TUPLE = object
|
|
14
|
+
|
|
15
|
+
version: str
|
|
16
|
+
__version__: str
|
|
17
|
+
__version_tuple__: VERSION_TUPLE
|
|
18
|
+
version_tuple: VERSION_TUPLE
|
|
19
|
+
|
|
20
|
+
__version__ = version = '1.0.0'
|
|
21
|
+
__version_tuple__ = version_tuple = (1, 0, 0)
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
from datetime import datetime, timedelta, time
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
|
|
4
|
+
from pandas import DataFrame
|
|
5
|
+
from xtquant import xtdata, xtdatacenter as xtdc
|
|
6
|
+
from filelock import FileLock, Timeout
|
|
7
|
+
|
|
8
|
+
from vnpy.trader.setting import SETTINGS
|
|
9
|
+
from vnpy.trader.constant import Exchange, Interval
|
|
10
|
+
from vnpy.trader.object import BarData, TickData, HistoryRequest
|
|
11
|
+
from vnpy.trader.utility import ZoneInfo, get_file_path
|
|
12
|
+
from vnpy.trader.datafeed import BaseDatafeed
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
INTERVAL_VT2XT: dict[Interval, str] = {
|
|
16
|
+
Interval.MINUTE: "1m",
|
|
17
|
+
Interval.DAILY: "1d",
|
|
18
|
+
Interval.TICK: "tick",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
INTERVAL_ADJUSTMENT_MAP: dict[Interval, timedelta] = {
|
|
22
|
+
Interval.MINUTE: timedelta(minutes=1),
|
|
23
|
+
Interval.DAILY: timedelta(), # 日线无需进行调整
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
EXCHANGE_VT2XT: dict[Exchange, str] = {
|
|
27
|
+
Exchange.SSE: "SH",
|
|
28
|
+
Exchange.SZSE: "SZ",
|
|
29
|
+
Exchange.BSE: "BJ",
|
|
30
|
+
Exchange.SHFE: "SF",
|
|
31
|
+
Exchange.CFFEX: "IF",
|
|
32
|
+
Exchange.INE: "INE",
|
|
33
|
+
Exchange.DCE: "DF",
|
|
34
|
+
Exchange.CZCE: "ZF",
|
|
35
|
+
Exchange.GFEX: "GF",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
CHINA_TZ = ZoneInfo("Asia/Shanghai")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class XtDatafeed(BaseDatafeed):
|
|
42
|
+
"""迅投研数据服务接口"""
|
|
43
|
+
|
|
44
|
+
lock_filename = "xt_lock"
|
|
45
|
+
lock_filepath = get_file_path(lock_filename)
|
|
46
|
+
|
|
47
|
+
def __init__(self):
|
|
48
|
+
""""""
|
|
49
|
+
self.username: str = SETTINGS["datafeed.username"]
|
|
50
|
+
self.password: str = SETTINGS["datafeed.password"]
|
|
51
|
+
self.inited: bool = False
|
|
52
|
+
|
|
53
|
+
self.lock: FileLock = None
|
|
54
|
+
|
|
55
|
+
xtdata.enable_hello = False
|
|
56
|
+
|
|
57
|
+
def init(self, output: Callable = print) -> bool:
|
|
58
|
+
"""初始化"""
|
|
59
|
+
if self.inited:
|
|
60
|
+
return True
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
# 使用Token连接,无需启动客户端
|
|
64
|
+
if self.username != "client":
|
|
65
|
+
self.init_xtdc()
|
|
66
|
+
|
|
67
|
+
# 尝试查询合约信息,确认连接成功
|
|
68
|
+
xtdata.get_instrument_detail("000001.SZ")
|
|
69
|
+
except Exception as ex:
|
|
70
|
+
output(f"迅投研数据服务初始化失败,发生异常:{ex}")
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
self.inited = True
|
|
74
|
+
return True
|
|
75
|
+
|
|
76
|
+
def get_lock(self) -> bool:
|
|
77
|
+
"""获取文件锁,确保单例运行"""
|
|
78
|
+
self.lock = FileLock(self.lock_filepath)
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
self.lock.acquire(timeout=1)
|
|
82
|
+
return True
|
|
83
|
+
except Timeout:
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
def init_xtdc(self) -> None:
|
|
87
|
+
"""初始化xtdc服务进程"""
|
|
88
|
+
if not self.get_lock():
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
# 设置token
|
|
92
|
+
xtdc.set_token(self.password)
|
|
93
|
+
|
|
94
|
+
# 设置连接池
|
|
95
|
+
xtdc.set_allow_optmize_address(["115.231.218.73:55310", "115.231.218.79:55310"])
|
|
96
|
+
|
|
97
|
+
# 开启使用期货真实夜盘时间
|
|
98
|
+
xtdc.set_future_realtime_mode(True)
|
|
99
|
+
|
|
100
|
+
# 执行初始化,但不启动默认58609端口监听
|
|
101
|
+
xtdc.init(False)
|
|
102
|
+
|
|
103
|
+
# 设置监听端口58620
|
|
104
|
+
xtdc.listen(port=58620)
|
|
105
|
+
|
|
106
|
+
def query_bar_history(self, req: HistoryRequest, output: Callable = print) -> list[BarData] | None:
|
|
107
|
+
"""查询K线数据"""
|
|
108
|
+
history: list[BarData] = []
|
|
109
|
+
|
|
110
|
+
if not self.inited:
|
|
111
|
+
n: bool = self.init(output)
|
|
112
|
+
if not n:
|
|
113
|
+
return history
|
|
114
|
+
|
|
115
|
+
df: DataFrame = get_history_df(req, output)
|
|
116
|
+
if df.empty:
|
|
117
|
+
return history
|
|
118
|
+
|
|
119
|
+
adjustment: timedelta = INTERVAL_ADJUSTMENT_MAP[req.interval]
|
|
120
|
+
|
|
121
|
+
# 遍历解析
|
|
122
|
+
auction_bar: BarData = None
|
|
123
|
+
|
|
124
|
+
for tp in df.itertuples():
|
|
125
|
+
# 将迅投研时间戳(K线结束时点)转换为VeighNa时间戳(K线开始时点)
|
|
126
|
+
dt: datetime = datetime.fromtimestamp(tp.time / 1000)
|
|
127
|
+
dt = dt.replace(tzinfo=CHINA_TZ)
|
|
128
|
+
dt = dt - adjustment
|
|
129
|
+
|
|
130
|
+
# 日线,过滤尚未走完的当日数据
|
|
131
|
+
if req.interval == Interval.DAILY:
|
|
132
|
+
incomplete_bar: bool = dt.date() == datetime.now().date() and datetime.now().time() < time(hour=15)
|
|
133
|
+
if incomplete_bar:
|
|
134
|
+
continue
|
|
135
|
+
# 分钟线,过滤盘前集合竞价数据(合并到开盘后第1根K线中)
|
|
136
|
+
else:
|
|
137
|
+
if (
|
|
138
|
+
req.exchange in (Exchange.SSE, Exchange.SZSE, Exchange.BSE, Exchange.CFFEX)
|
|
139
|
+
and dt.time() == time(hour=9, minute=29)
|
|
140
|
+
) or (
|
|
141
|
+
req.exchange
|
|
142
|
+
in (
|
|
143
|
+
Exchange.SHFE,
|
|
144
|
+
Exchange.INE,
|
|
145
|
+
Exchange.DCE,
|
|
146
|
+
Exchange.CZCE,
|
|
147
|
+
Exchange.GFEX,
|
|
148
|
+
)
|
|
149
|
+
and dt.time() in (time(hour=8, minute=59), time(hour=20, minute=59))
|
|
150
|
+
):
|
|
151
|
+
auction_bar = BarData(
|
|
152
|
+
symbol=req.symbol,
|
|
153
|
+
exchange=req.exchange,
|
|
154
|
+
datetime=dt,
|
|
155
|
+
open_price=float(tp.open),
|
|
156
|
+
volume=float(tp.volume),
|
|
157
|
+
turnover=float(tp.amount),
|
|
158
|
+
gateway_name="XT",
|
|
159
|
+
)
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
# 生成K线对象
|
|
163
|
+
bar: BarData = BarData(
|
|
164
|
+
symbol=req.symbol,
|
|
165
|
+
exchange=req.exchange,
|
|
166
|
+
datetime=dt,
|
|
167
|
+
interval=req.interval,
|
|
168
|
+
volume=float(tp.volume),
|
|
169
|
+
turnover=float(tp.amount),
|
|
170
|
+
open_interest=float(tp.openInterest),
|
|
171
|
+
open_price=float(tp.open),
|
|
172
|
+
high_price=float(tp.high),
|
|
173
|
+
low_price=float(tp.low),
|
|
174
|
+
close_price=float(tp.close),
|
|
175
|
+
gateway_name="XT",
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# 合并集合竞价数据
|
|
179
|
+
if auction_bar and auction_bar.volume:
|
|
180
|
+
bar.open_price = auction_bar.open_price
|
|
181
|
+
bar.volume += auction_bar.volume
|
|
182
|
+
bar.turnover += auction_bar.turnover
|
|
183
|
+
auction_bar = None
|
|
184
|
+
|
|
185
|
+
history.append(bar)
|
|
186
|
+
|
|
187
|
+
return history
|
|
188
|
+
|
|
189
|
+
def query_tick_history(self, req: HistoryRequest, output: Callable = print) -> list[TickData] | None:
|
|
190
|
+
"""查询Tick数据"""
|
|
191
|
+
history: list[TickData] = []
|
|
192
|
+
|
|
193
|
+
if not self.inited:
|
|
194
|
+
n: bool = self.init(output)
|
|
195
|
+
if not n:
|
|
196
|
+
return history
|
|
197
|
+
|
|
198
|
+
df: DataFrame = get_history_df(req, output)
|
|
199
|
+
if df.empty:
|
|
200
|
+
return history
|
|
201
|
+
|
|
202
|
+
# 遍历解析
|
|
203
|
+
for tp in df.itertuples():
|
|
204
|
+
dt: datetime = datetime.fromtimestamp(tp.time / 1000)
|
|
205
|
+
dt = dt.replace(tzinfo=CHINA_TZ)
|
|
206
|
+
|
|
207
|
+
tick: TickData = TickData(
|
|
208
|
+
symbol=req.symbol,
|
|
209
|
+
exchange=req.exchange,
|
|
210
|
+
datetime=dt,
|
|
211
|
+
volume=float(tp.volume),
|
|
212
|
+
turnover=float(tp.amount),
|
|
213
|
+
open_interest=float(tp.openInt),
|
|
214
|
+
open_price=float(tp.open),
|
|
215
|
+
high_price=float(tp.high),
|
|
216
|
+
low_price=float(tp.low),
|
|
217
|
+
last_price=float(tp.lastPrice),
|
|
218
|
+
pre_close=float(tp.lastClose),
|
|
219
|
+
bid_price_1=float(tp.bidPrice[0]),
|
|
220
|
+
ask_price_1=float(tp.askPrice[0]),
|
|
221
|
+
bid_volume_1=float(tp.bidVol[0]),
|
|
222
|
+
ask_volume_1=float(tp.askVol[0]),
|
|
223
|
+
gateway_name="XT",
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
bid_price_2: float = float(tp.bidPrice[1])
|
|
227
|
+
if bid_price_2:
|
|
228
|
+
tick.bid_price_2 = bid_price_2
|
|
229
|
+
tick.bid_price_3 = float(tp.bidPrice[2])
|
|
230
|
+
tick.bid_price_4 = float(tp.bidPrice[3])
|
|
231
|
+
tick.bid_price_5 = float(tp.bidPrice[4])
|
|
232
|
+
|
|
233
|
+
tick.ask_price_2 = float(tp.askPrice[1])
|
|
234
|
+
tick.ask_price_3 = float(tp.askPrice[2])
|
|
235
|
+
tick.ask_price_4 = float(tp.askPrice[3])
|
|
236
|
+
tick.ask_price_5 = float(tp.askPrice[4])
|
|
237
|
+
|
|
238
|
+
tick.bid_volume_2 = float(tp.bidVol[1])
|
|
239
|
+
tick.bid_volume_3 = float(tp.bidVol[2])
|
|
240
|
+
tick.bid_volume_4 = float(tp.bidVol[3])
|
|
241
|
+
tick.bid_volume_5 = float(tp.bidVol[4])
|
|
242
|
+
|
|
243
|
+
tick.ask_volume_2 = float(tp.askVol[1])
|
|
244
|
+
tick.ask_volume_3 = float(tp.askVol[2])
|
|
245
|
+
tick.ask_volume_4 = float(tp.askVol[3])
|
|
246
|
+
tick.ask_volume_5 = float(tp.askVol[4])
|
|
247
|
+
|
|
248
|
+
history.append(tick)
|
|
249
|
+
|
|
250
|
+
return history
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def get_history_df(req: HistoryRequest, output: Callable = print) -> DataFrame:
|
|
254
|
+
"""获取历史数据DataFrame"""
|
|
255
|
+
symbol: str = req.symbol
|
|
256
|
+
exchange: Exchange = req.exchange
|
|
257
|
+
start: datetime = req.start
|
|
258
|
+
end: datetime = req.end
|
|
259
|
+
interval: Interval = req.interval
|
|
260
|
+
|
|
261
|
+
if not interval:
|
|
262
|
+
interval = Interval.TICK
|
|
263
|
+
|
|
264
|
+
xt_interval: str = INTERVAL_VT2XT.get(interval, None)
|
|
265
|
+
if not xt_interval:
|
|
266
|
+
output(f"迅投研查询历史数据失败:不支持的时间周期{interval.value}")
|
|
267
|
+
return DataFrame()
|
|
268
|
+
|
|
269
|
+
# 为了查询夜盘数据
|
|
270
|
+
end += timedelta(1)
|
|
271
|
+
|
|
272
|
+
# 从服务器下载获取
|
|
273
|
+
xt_symbol: str = symbol + "." + EXCHANGE_VT2XT[exchange]
|
|
274
|
+
start: str = start.strftime("%Y%m%d%H%M%S")
|
|
275
|
+
end: str = end.strftime("%Y%m%d%H%M%S")
|
|
276
|
+
|
|
277
|
+
if exchange in (Exchange.SSE, Exchange.SZSE) and len(symbol) > 6:
|
|
278
|
+
xt_symbol += "O"
|
|
279
|
+
|
|
280
|
+
xtdata.download_history_data(xt_symbol, xt_interval, start, end)
|
|
281
|
+
data: dict = xtdata.get_local_data(
|
|
282
|
+
[], [xt_symbol], xt_interval, start, end, -1, "front_ratio", False
|
|
283
|
+
) # 默认等比前复权
|
|
284
|
+
|
|
285
|
+
df: DataFrame = data[xt_symbol]
|
|
286
|
+
return df
|
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
# @Time : 2025/4/28 19:13
|
|
2
|
+
# @Author : YQ Tsui
|
|
3
|
+
# @File : xt_datapub.py
|
|
4
|
+
# @Purpose :
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
|
|
8
|
+
from xtquant import xtdata, xtdatacenter as xtdc
|
|
9
|
+
from filelock import FileLock, Timeout
|
|
10
|
+
|
|
11
|
+
from vnpy.trader.gateway import BaseGateway
|
|
12
|
+
from vnpy.trader.object import (
|
|
13
|
+
SubscribeRequest,
|
|
14
|
+
ContractData,
|
|
15
|
+
TickData,
|
|
16
|
+
OptionType,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
from vnpy.trader.constant import Exchange, Product
|
|
20
|
+
from vnpy.trader.utility import ZoneInfo, get_file_path, round_to
|
|
21
|
+
|
|
22
|
+
EXCHANGE_VT2XT: dict[Exchange, str] = {
|
|
23
|
+
Exchange.SSE: "SH",
|
|
24
|
+
Exchange.SZSE: "SZ",
|
|
25
|
+
Exchange.BSE: "BJ",
|
|
26
|
+
Exchange.SHFE: "SF",
|
|
27
|
+
Exchange.CFFEX: "IF",
|
|
28
|
+
Exchange.INE: "INE",
|
|
29
|
+
Exchange.DCE: "DF",
|
|
30
|
+
Exchange.CZCE: "ZF",
|
|
31
|
+
Exchange.GFEX: "GF",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
EXCHANGE_XT2VT: dict[str, Exchange] = {v: k for k, v in EXCHANGE_VT2XT.items()}
|
|
35
|
+
EXCHANGE_XT2VT.update(
|
|
36
|
+
{
|
|
37
|
+
"CFFEX": Exchange.CFFEX,
|
|
38
|
+
"SHFE": Exchange.SHFE,
|
|
39
|
+
"CZCE": Exchange.CZCE,
|
|
40
|
+
"DCE": Exchange.DCE,
|
|
41
|
+
"GFEX": Exchange.GFEX,
|
|
42
|
+
"SHO": Exchange.SSE,
|
|
43
|
+
"SZO": Exchange.SZSE,
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# 其他常量
|
|
48
|
+
CHINA_TZ = ZoneInfo("Asia/Shanghai") # 中国时区
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# 全局缓存字典
|
|
52
|
+
symbol_contract_map: dict[(str, Exchange), ContractData] = {} # 合约数据
|
|
53
|
+
symbol_limit_map: dict[str, tuple[float, float]] = {} # 涨跌停价
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class XtMdApi:
|
|
57
|
+
"""行情API"""
|
|
58
|
+
|
|
59
|
+
lock_filename = "xt_lock"
|
|
60
|
+
lock_filepath = get_file_path(lock_filename)
|
|
61
|
+
|
|
62
|
+
def __init__(self, gateway: BaseGateway) -> None:
|
|
63
|
+
"""构造函数"""
|
|
64
|
+
self.gateway: BaseGateway = gateway
|
|
65
|
+
self.gateway_name: str = gateway.gateway_name
|
|
66
|
+
|
|
67
|
+
self.inited: bool = False
|
|
68
|
+
self.subscribed: set = set()
|
|
69
|
+
|
|
70
|
+
self.ip: str = ""
|
|
71
|
+
self.port: int = 0
|
|
72
|
+
self.token: str = ""
|
|
73
|
+
self.stock_active: bool = False
|
|
74
|
+
self.futures_active: bool = False
|
|
75
|
+
self.option_active: bool = False
|
|
76
|
+
self.fut_option_active: bool = False
|
|
77
|
+
|
|
78
|
+
self.available_exchange: list[Exchange] = []
|
|
79
|
+
|
|
80
|
+
def onMarketData(self, data: dict) -> None:
|
|
81
|
+
"""行情推送回调"""
|
|
82
|
+
|
|
83
|
+
def parse_data_dict(xt_symbol, d: dict) -> TickData:
|
|
84
|
+
symbol, xt_exchange = xt_symbol.split(".")
|
|
85
|
+
exchange = EXCHANGE_XT2VT[xt_exchange]
|
|
86
|
+
|
|
87
|
+
tick: TickData = TickData(
|
|
88
|
+
symbol=symbol,
|
|
89
|
+
exchange=exchange,
|
|
90
|
+
datetime=generate_datetime(d["time"]),
|
|
91
|
+
volume=d["volume"],
|
|
92
|
+
turnover=d["amount"],
|
|
93
|
+
open_interest=d["openInt"],
|
|
94
|
+
gateway_name=self.gateway_name,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
contract = symbol_contract_map[(tick.symbol, tick.exchange)]
|
|
98
|
+
tick.name = contract.name
|
|
99
|
+
|
|
100
|
+
bp_data: list = d["bidPrice"]
|
|
101
|
+
ap_data: list = d["askPrice"]
|
|
102
|
+
bv_data: list = d["bidVol"]
|
|
103
|
+
av_data: list = d["askVol"]
|
|
104
|
+
|
|
105
|
+
tick.bid_price_1 = round_to(bp_data[0], contract.pricetick)
|
|
106
|
+
tick.bid_price_2 = round_to(bp_data[1], contract.pricetick)
|
|
107
|
+
tick.bid_price_3 = round_to(bp_data[2], contract.pricetick)
|
|
108
|
+
tick.bid_price_4 = round_to(bp_data[3], contract.pricetick)
|
|
109
|
+
tick.bid_price_5 = round_to(bp_data[4], contract.pricetick)
|
|
110
|
+
|
|
111
|
+
tick.ask_price_1 = round_to(ap_data[0], contract.pricetick)
|
|
112
|
+
tick.ask_price_2 = round_to(ap_data[1], contract.pricetick)
|
|
113
|
+
tick.ask_price_3 = round_to(ap_data[2], contract.pricetick)
|
|
114
|
+
tick.ask_price_4 = round_to(ap_data[3], contract.pricetick)
|
|
115
|
+
tick.ask_price_5 = round_to(ap_data[4], contract.pricetick)
|
|
116
|
+
|
|
117
|
+
tick.bid_volume_1 = bv_data[0]
|
|
118
|
+
tick.bid_volume_2 = bv_data[1]
|
|
119
|
+
tick.bid_volume_3 = bv_data[2]
|
|
120
|
+
tick.bid_volume_4 = bv_data[3]
|
|
121
|
+
tick.bid_volume_5 = bv_data[4]
|
|
122
|
+
|
|
123
|
+
tick.ask_volume_1 = av_data[0]
|
|
124
|
+
tick.ask_volume_2 = av_data[1]
|
|
125
|
+
tick.ask_volume_3 = av_data[2]
|
|
126
|
+
tick.ask_volume_4 = av_data[3]
|
|
127
|
+
tick.ask_volume_5 = av_data[4]
|
|
128
|
+
|
|
129
|
+
tick.last_price = round_to(d["lastPrice"], contract.pricetick)
|
|
130
|
+
tick.open_price = round_to(d["open"], contract.pricetick)
|
|
131
|
+
tick.high_price = round_to(d["high"], contract.pricetick)
|
|
132
|
+
tick.low_price = round_to(d["low"], contract.pricetick)
|
|
133
|
+
tick.pre_close = round_to(d["lastClose"], contract.pricetick)
|
|
134
|
+
|
|
135
|
+
if tick.vt_symbol in symbol_limit_map:
|
|
136
|
+
tick.limit_up, tick.limit_down = symbol_limit_map[tick.vt_symbol]
|
|
137
|
+
return tick
|
|
138
|
+
|
|
139
|
+
for xt_symbol, buf in data.items():
|
|
140
|
+
if isinstance(buf, dict):
|
|
141
|
+
tick = parse_data_dict(xt_symbol, buf)
|
|
142
|
+
self.gateway.on_tick(tick)
|
|
143
|
+
elif isinstance(buf, list):
|
|
144
|
+
for d in buf:
|
|
145
|
+
tick = parse_data_dict(xt_symbol, d)
|
|
146
|
+
self.gateway.on_tick(tick)
|
|
147
|
+
|
|
148
|
+
def connect(
|
|
149
|
+
self,
|
|
150
|
+
ip: str,
|
|
151
|
+
port: int,
|
|
152
|
+
token: str,
|
|
153
|
+
stock_active: bool,
|
|
154
|
+
futures_active: bool,
|
|
155
|
+
option_active: bool,
|
|
156
|
+
fut_option_active: bool,
|
|
157
|
+
vip_server_only: bool = True,
|
|
158
|
+
) -> None:
|
|
159
|
+
"""连接"""
|
|
160
|
+
self.gateway.write_log("开始启动行情服务,请稍等")
|
|
161
|
+
|
|
162
|
+
self.ip = ip
|
|
163
|
+
self.port = port
|
|
164
|
+
self.token = token
|
|
165
|
+
self.stock_active = stock_active
|
|
166
|
+
self.futures_active = futures_active
|
|
167
|
+
self.option_active = option_active
|
|
168
|
+
self.fut_option_active = fut_option_active
|
|
169
|
+
|
|
170
|
+
if self.inited:
|
|
171
|
+
self.gateway.write_log("行情接口已经初始化,请勿重复操作")
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
xtdata.watch_xtquant_status(self.on_connection_status)
|
|
175
|
+
if self.ip == "" or self.port <= 0 or not self._connect_to_existing_xtdc(self.ip, self.port):
|
|
176
|
+
if self.ip in {"127.0.0.1", "localhost"}:
|
|
177
|
+
_, self.port = self.init_xtdc(vip_server_only)
|
|
178
|
+
client = xtdata.connect(port=self.port)
|
|
179
|
+
if not client.is_connected():
|
|
180
|
+
self.gateway.write_log("迅投研数据服务初始化失败,请检查日志")
|
|
181
|
+
return
|
|
182
|
+
else:
|
|
183
|
+
self.gateway.write_log(f"远程迅投研数据服务{self.ip}:{self.port}连接失败")
|
|
184
|
+
|
|
185
|
+
def _connect_to_existing_xtdc(self, ip: str, port: int) -> bool:
|
|
186
|
+
"""连接到已经存在的行情服务"""
|
|
187
|
+
try:
|
|
188
|
+
client = xtdata.connect(ip, port)
|
|
189
|
+
if client.is_connected():
|
|
190
|
+
self.gateway.write_log("连接到已经存在的行情服务")
|
|
191
|
+
return True
|
|
192
|
+
return False
|
|
193
|
+
except Exception:
|
|
194
|
+
return False
|
|
195
|
+
|
|
196
|
+
def get_lock(self) -> bool:
|
|
197
|
+
"""获取文件锁,确保单例运行"""
|
|
198
|
+
self.lock = FileLock(self.lock_filepath)
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
self.lock.acquire(timeout=1)
|
|
202
|
+
return True
|
|
203
|
+
except Timeout:
|
|
204
|
+
return False
|
|
205
|
+
|
|
206
|
+
def init_xtdc(self, vip_server_only) -> int:
|
|
207
|
+
"""初始化xtdc服务进程"""
|
|
208
|
+
if not self.get_lock():
|
|
209
|
+
return 0
|
|
210
|
+
|
|
211
|
+
# 设置token
|
|
212
|
+
xtdc.set_token(self.token)
|
|
213
|
+
|
|
214
|
+
# 设置是否只连接VIP服务器
|
|
215
|
+
if vip_server_only:
|
|
216
|
+
xtdc.set_allow_optmize_address(
|
|
217
|
+
[
|
|
218
|
+
"115.231.218.73:55310",
|
|
219
|
+
"115.231.218.79:55310",
|
|
220
|
+
"218.16.123.11:55310",
|
|
221
|
+
"218.16.123.27:55310",
|
|
222
|
+
]
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# 开启使用期货真实夜盘时间
|
|
226
|
+
xtdc.set_future_realtime_mode(True)
|
|
227
|
+
|
|
228
|
+
# 执行初始化,但不启动默认58609端口监听
|
|
229
|
+
xtdc.init(False)
|
|
230
|
+
|
|
231
|
+
# 设置监听端口58620
|
|
232
|
+
return xtdc.listen(port=(self.port, self.port + 50))
|
|
233
|
+
|
|
234
|
+
def on_connection_status(self, info_dict: dict) -> None:
|
|
235
|
+
"""连接状态回调"""
|
|
236
|
+
if self.inited:
|
|
237
|
+
if info_dict["status"] != "connected":
|
|
238
|
+
self.inited = False
|
|
239
|
+
error_info = info_dict.get("error", None)
|
|
240
|
+
if error_info:
|
|
241
|
+
self.gateway.write_log(f"[{error_info['error id']}]{error_info['error']}")
|
|
242
|
+
self.gateway.write_log(f"行情服务({info_dict['address']})连接断开")
|
|
243
|
+
elif info_dict["status"] == "connected":
|
|
244
|
+
self.gateway.write_log(f"行情服务({info_dict['address']})连接成功")
|
|
245
|
+
self.inited = True
|
|
246
|
+
self.gateway.write_log("开始查询合约")
|
|
247
|
+
self.query_contracts()
|
|
248
|
+
|
|
249
|
+
def query_contracts(self) -> None:
|
|
250
|
+
"""查询合约信息"""
|
|
251
|
+
if self.stock_active:
|
|
252
|
+
self.query_stock_contracts()
|
|
253
|
+
|
|
254
|
+
if self.futures_active:
|
|
255
|
+
self.query_future_contracts()
|
|
256
|
+
|
|
257
|
+
if self.option_active:
|
|
258
|
+
self.query_option_contracts()
|
|
259
|
+
|
|
260
|
+
self.gateway.write_log("合约信息查询成功")
|
|
261
|
+
|
|
262
|
+
def query_stock_contracts(self) -> None:
|
|
263
|
+
"""查询股票合约信息"""
|
|
264
|
+
xt_symbols: list[str] = []
|
|
265
|
+
markets: list = ["沪深A股", "沪深转债", "沪深ETF", "沪深指数", "京市A股"]
|
|
266
|
+
new_exchanges = [Exchange.SSE, Exchange.SZSE, Exchange.BSE]
|
|
267
|
+
new_exchanges = [exchange for exchange in new_exchanges if exchange not in self.available_exchange]
|
|
268
|
+
self.available_exchange.extend(new_exchanges)
|
|
269
|
+
|
|
270
|
+
for i in markets:
|
|
271
|
+
names: list = xtdata.get_stock_list_in_sector(i)
|
|
272
|
+
xt_symbols.extend(names)
|
|
273
|
+
|
|
274
|
+
for xt_symbol in xt_symbols:
|
|
275
|
+
# 筛选需要的合约
|
|
276
|
+
product = None
|
|
277
|
+
symbol, xt_exchange = xt_symbol.split(".")
|
|
278
|
+
|
|
279
|
+
if xt_exchange == "SZ":
|
|
280
|
+
if xt_symbol.startswith("00"):
|
|
281
|
+
product = Product.EQUITY
|
|
282
|
+
elif xt_symbol.startswith("159"):
|
|
283
|
+
product = Product.ETF
|
|
284
|
+
else:
|
|
285
|
+
product = Product.INDEX
|
|
286
|
+
elif xt_exchange == "SH":
|
|
287
|
+
if xt_symbol.startswith(("60", "68")):
|
|
288
|
+
product = Product.EQUITY
|
|
289
|
+
elif xt_symbol.startswith("5"):
|
|
290
|
+
product = Product.ETF
|
|
291
|
+
else:
|
|
292
|
+
product = Product.INDEX
|
|
293
|
+
elif xt_exchange == "BJ":
|
|
294
|
+
product = Product.EQUITY
|
|
295
|
+
|
|
296
|
+
if not product:
|
|
297
|
+
continue
|
|
298
|
+
|
|
299
|
+
# 生成并推送合约信息
|
|
300
|
+
data: dict = xtdata.get_instrument_detail(xt_symbol)
|
|
301
|
+
if data is None:
|
|
302
|
+
self.gateway.write_log(f"合约{xt_symbol}信息查询失败")
|
|
303
|
+
continue
|
|
304
|
+
exch = EXCHANGE_XT2VT[xt_exchange]
|
|
305
|
+
|
|
306
|
+
contract: ContractData = ContractData(
|
|
307
|
+
symbol=symbol,
|
|
308
|
+
exchange=exch,
|
|
309
|
+
name=data["InstrumentName"],
|
|
310
|
+
product=product,
|
|
311
|
+
size=data["VolumeMultiple"],
|
|
312
|
+
pricetick=data["PriceTick"],
|
|
313
|
+
history_data=False,
|
|
314
|
+
gateway_name=self.gateway_name,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
symbol_contract_map[(symbol, exch)] = contract
|
|
318
|
+
symbol_limit_map[contract.vt_symbol] = (
|
|
319
|
+
data["UpStopPrice"],
|
|
320
|
+
data["DownStopPrice"],
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
self.gateway.on_contract(contract)
|
|
324
|
+
|
|
325
|
+
def query_future_contracts(self) -> None:
|
|
326
|
+
"""查询期货合约信息"""
|
|
327
|
+
xt_symbols: list[str] = []
|
|
328
|
+
markets: list = [
|
|
329
|
+
"中金所期货",
|
|
330
|
+
"上期所期货",
|
|
331
|
+
"能源中心期货",
|
|
332
|
+
"大商所期货",
|
|
333
|
+
"郑商所期货",
|
|
334
|
+
"广期所期货",
|
|
335
|
+
]
|
|
336
|
+
new_exchanges = [
|
|
337
|
+
Exchange.SHFE,
|
|
338
|
+
Exchange.CFFEX,
|
|
339
|
+
Exchange.INE,
|
|
340
|
+
Exchange.DCE,
|
|
341
|
+
Exchange.CZCE,
|
|
342
|
+
Exchange.GFEX,
|
|
343
|
+
]
|
|
344
|
+
new_exchanges = [exchange for exchange in new_exchanges if exchange not in self.available_exchange]
|
|
345
|
+
self.available_exchange.extend(new_exchanges)
|
|
346
|
+
|
|
347
|
+
for i in markets:
|
|
348
|
+
names: list = xtdata.get_stock_list_in_sector(i)
|
|
349
|
+
xt_symbols.extend(names)
|
|
350
|
+
|
|
351
|
+
for xt_symbol in xt_symbols:
|
|
352
|
+
# 筛选需要的合约
|
|
353
|
+
product = None
|
|
354
|
+
symbol, xt_exchange = xt_symbol.split(".")
|
|
355
|
+
|
|
356
|
+
if xt_exchange == "ZF" and len(symbol) > 6 and "&" not in symbol:
|
|
357
|
+
product = Product.OPTION
|
|
358
|
+
elif xt_exchange in ("IF", "GF") and "-" in symbol:
|
|
359
|
+
product = Product.OPTION
|
|
360
|
+
elif xt_exchange in ("DF", "INE", "SF") and ("C" in symbol or "P" in symbol) and "SP" not in symbol:
|
|
361
|
+
product = Product.OPTION
|
|
362
|
+
else:
|
|
363
|
+
product = Product.FUTURES
|
|
364
|
+
|
|
365
|
+
# 生成并推送合约信息
|
|
366
|
+
if product == Product.OPTION:
|
|
367
|
+
data: dict = xtdata.get_instrument_detail(xt_symbol, True)
|
|
368
|
+
else:
|
|
369
|
+
data: dict = xtdata.get_instrument_detail(xt_symbol)
|
|
370
|
+
|
|
371
|
+
if not data["ExpireDate"]:
|
|
372
|
+
if "00" not in symbol:
|
|
373
|
+
continue
|
|
374
|
+
|
|
375
|
+
exch = EXCHANGE_XT2VT[xt_exchange]
|
|
376
|
+
contract: ContractData = ContractData(
|
|
377
|
+
symbol=symbol,
|
|
378
|
+
exchange=exch,
|
|
379
|
+
name=data["InstrumentName"],
|
|
380
|
+
product=product,
|
|
381
|
+
size=data["VolumeMultiple"],
|
|
382
|
+
pricetick=data["PriceTick"],
|
|
383
|
+
history_data=False,
|
|
384
|
+
gateway_name=self.gateway_name,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
symbol_contract_map[(symbol, exch)] = contract
|
|
388
|
+
symbol_limit_map[contract.vt_symbol] = (
|
|
389
|
+
data["UpStopPrice"],
|
|
390
|
+
data["DownStopPrice"],
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
self.gateway.on_contract(contract)
|
|
394
|
+
|
|
395
|
+
def query_option_contracts(self) -> None:
|
|
396
|
+
"""查询期权合约信息"""
|
|
397
|
+
xt_symbols: list[str] = []
|
|
398
|
+
|
|
399
|
+
markets: list[str] = []
|
|
400
|
+
new_exchanges: list[Exchange] = []
|
|
401
|
+
if self.option_active:
|
|
402
|
+
markets.extend(
|
|
403
|
+
[
|
|
404
|
+
"上证期权",
|
|
405
|
+
"深证期权",
|
|
406
|
+
]
|
|
407
|
+
)
|
|
408
|
+
new_exchanges.extend([Exchange.SSE, Exchange.SZSE])
|
|
409
|
+
if self.fut_option_active:
|
|
410
|
+
markets.extend(
|
|
411
|
+
[
|
|
412
|
+
"中金所期权",
|
|
413
|
+
"上期所期权",
|
|
414
|
+
"能源中心期权",
|
|
415
|
+
"大商所期权",
|
|
416
|
+
"郑商所期权",
|
|
417
|
+
"广期所期权",
|
|
418
|
+
]
|
|
419
|
+
)
|
|
420
|
+
new_exchanges.extend(
|
|
421
|
+
[
|
|
422
|
+
Exchange.CFFEX,
|
|
423
|
+
Exchange.SHFE,
|
|
424
|
+
Exchange.INE,
|
|
425
|
+
Exchange.DCE,
|
|
426
|
+
Exchange.CZCE,
|
|
427
|
+
Exchange.GFEX,
|
|
428
|
+
]
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
new_exchanges = [exchange for exchange in new_exchanges if exchange not in self.available_exchange]
|
|
432
|
+
self.available_exchange.extend(new_exchanges)
|
|
433
|
+
|
|
434
|
+
for i in markets:
|
|
435
|
+
names: list = xtdata.get_stock_list_in_sector(i)
|
|
436
|
+
xt_symbols.extend(names)
|
|
437
|
+
|
|
438
|
+
for xt_symbol in xt_symbols:
|
|
439
|
+
""""""
|
|
440
|
+
_, xt_exchange = xt_symbol.split(".")
|
|
441
|
+
|
|
442
|
+
if xt_exchange in {"SHO", "SZO"}:
|
|
443
|
+
contract = process_etf_option(xtdata.get_instrument_detail, xt_symbol, self.gateway_name)
|
|
444
|
+
else:
|
|
445
|
+
contract = process_futures_option(xtdata.get_instrument_detail, xt_symbol, self.gateway_name)
|
|
446
|
+
|
|
447
|
+
if contract:
|
|
448
|
+
# for CZCE options, xt will return two contracts for one option, one with one digit for year, which is the current official contract, and one with two digits for year, which is for long history convience, we just ignore the latter
|
|
449
|
+
if (contract.symbol, contract.exchange) in symbol_contract_map:
|
|
450
|
+
continue
|
|
451
|
+
symbol_contract_map[(contract.symbol, contract.exchange)] = contract
|
|
452
|
+
|
|
453
|
+
self.gateway.on_contract(contract)
|
|
454
|
+
|
|
455
|
+
def subscribe(self, req: SubscribeRequest) -> None:
|
|
456
|
+
"""订阅行情"""
|
|
457
|
+
if (req.symbol, req.exchange) not in symbol_contract_map:
|
|
458
|
+
return
|
|
459
|
+
|
|
460
|
+
xt_exchange: str = EXCHANGE_VT2XT[req.exchange]
|
|
461
|
+
if xt_exchange in {"SH", "SZ"} and len(req.symbol) > 6:
|
|
462
|
+
xt_exchange += "O"
|
|
463
|
+
|
|
464
|
+
xt_symbol: str = req.symbol + "." + xt_exchange
|
|
465
|
+
|
|
466
|
+
if xt_symbol not in self.subscribed:
|
|
467
|
+
# xtdata.subscribe_quote(stock_code=xt_symbol, period="tick", callback=self.onMarketData)
|
|
468
|
+
xtdata.subscribe_whole_quote([xt_symbol], callback=self.onMarketData)
|
|
469
|
+
self.subscribed.add(xt_symbol)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def generate_datetime(timestamp: int, millisecond: bool = True) -> datetime:
|
|
473
|
+
"""生成本地时间"""
|
|
474
|
+
if millisecond:
|
|
475
|
+
dt: datetime = datetime.fromtimestamp(timestamp / 1000)
|
|
476
|
+
else:
|
|
477
|
+
dt: datetime = datetime.fromtimestamp(timestamp)
|
|
478
|
+
dt: datetime = dt.replace(tzinfo=CHINA_TZ)
|
|
479
|
+
return dt
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def process_etf_option(get_instrument_detail: Callable, xt_symbol: str, gateway_name: str) -> ContractData | None:
|
|
483
|
+
"""处理ETF期权"""
|
|
484
|
+
# 拆分XT代码
|
|
485
|
+
symbol, xt_exchange = xt_symbol.split(".")
|
|
486
|
+
|
|
487
|
+
# 筛选期权合约合约(ETF期权代码为8位)
|
|
488
|
+
if len(symbol) != 8:
|
|
489
|
+
return None
|
|
490
|
+
|
|
491
|
+
# 查询转换数据
|
|
492
|
+
data: dict = get_instrument_detail(xt_symbol, True)
|
|
493
|
+
|
|
494
|
+
name: str = data["InstrumentName"]
|
|
495
|
+
if "购" in name:
|
|
496
|
+
option_type = OptionType.CALL
|
|
497
|
+
elif "沽" in name:
|
|
498
|
+
option_type = OptionType.PUT
|
|
499
|
+
else:
|
|
500
|
+
return None
|
|
501
|
+
|
|
502
|
+
if "A" in name:
|
|
503
|
+
option_index = str(data["OptExercisePrice"]) + "-A"
|
|
504
|
+
else:
|
|
505
|
+
option_index = str(data["OptExercisePrice"]) + "-M"
|
|
506
|
+
|
|
507
|
+
contract: ContractData = ContractData(
|
|
508
|
+
symbol=data["InstrumentID"],
|
|
509
|
+
exchange=EXCHANGE_XT2VT[xt_exchange],
|
|
510
|
+
name=data["InstrumentName"],
|
|
511
|
+
product=Product.OPTION,
|
|
512
|
+
size=data["VolumeMultiple"],
|
|
513
|
+
pricetick=data["PriceTick"],
|
|
514
|
+
min_volume=data["MinLimitOrderVolume"],
|
|
515
|
+
option_strike=data["OptExercisePrice"],
|
|
516
|
+
option_listed=datetime.strptime(data["OpenDate"], "%Y%m%d"),
|
|
517
|
+
option_expiry=datetime.strptime(data["ExpireDate"], "%Y%m%d"),
|
|
518
|
+
option_portfolio=data["OptUndlCode"] + "_O",
|
|
519
|
+
option_index=option_index,
|
|
520
|
+
option_type=option_type,
|
|
521
|
+
option_underlying=data["OptUndlCode"] + "-" + str(data["ExpireDate"])[:6],
|
|
522
|
+
gateway_name=gateway_name,
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
symbol_limit_map[contract.vt_symbol] = (data["UpStopPrice"], data["DownStopPrice"])
|
|
526
|
+
|
|
527
|
+
return contract
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def process_futures_option(get_instrument_detail: Callable, xt_symbol: str, gateway_name: str) -> ContractData | None:
|
|
531
|
+
"""处理期货期权"""
|
|
532
|
+
# 筛选期权合约
|
|
533
|
+
data: dict = get_instrument_detail(xt_symbol, True)
|
|
534
|
+
|
|
535
|
+
option_strike: float = data["OptExercisePrice"]
|
|
536
|
+
if not option_strike:
|
|
537
|
+
return None
|
|
538
|
+
|
|
539
|
+
# 拆分XT代码
|
|
540
|
+
symbol, xt_exchange = xt_symbol.split(".")
|
|
541
|
+
|
|
542
|
+
# 移除产品前缀
|
|
543
|
+
ix = 0
|
|
544
|
+
for ix, w in enumerate(symbol): # noqa
|
|
545
|
+
if w.isdigit():
|
|
546
|
+
break
|
|
547
|
+
|
|
548
|
+
suffix: str = symbol[ix:]
|
|
549
|
+
|
|
550
|
+
# 过滤非期权合约
|
|
551
|
+
if "(" in symbol or " " in symbol:
|
|
552
|
+
return None
|
|
553
|
+
|
|
554
|
+
# 判断期权类型
|
|
555
|
+
if "C" in suffix:
|
|
556
|
+
option_type = OptionType.CALL
|
|
557
|
+
elif "P" in suffix:
|
|
558
|
+
option_type = OptionType.PUT
|
|
559
|
+
else:
|
|
560
|
+
return None
|
|
561
|
+
|
|
562
|
+
# 获取期权标的
|
|
563
|
+
if "-" in symbol:
|
|
564
|
+
option_underlying: str = symbol.split("-")[0]
|
|
565
|
+
else:
|
|
566
|
+
option_underlying: str = data["OptUndlCode"]
|
|
567
|
+
|
|
568
|
+
# 转换数据
|
|
569
|
+
contract: ContractData = ContractData(
|
|
570
|
+
symbol=data["InstrumentID"],
|
|
571
|
+
exchange=EXCHANGE_XT2VT[xt_exchange],
|
|
572
|
+
name=data["InstrumentName"],
|
|
573
|
+
product=Product.OPTION,
|
|
574
|
+
size=data["VolumeMultiple"],
|
|
575
|
+
pricetick=data["PriceTick"],
|
|
576
|
+
min_volume=data["MinLimitOrderVolume"],
|
|
577
|
+
option_strike=data["OptExercisePrice"],
|
|
578
|
+
option_listed=datetime.strptime(data["OpenDate"], "%Y%m%d"),
|
|
579
|
+
option_expiry=datetime.strptime(data["ExpireDate"], "%Y%m%d"),
|
|
580
|
+
option_index=str(data["OptExercisePrice"]),
|
|
581
|
+
option_type=option_type,
|
|
582
|
+
option_underlying=option_underlying,
|
|
583
|
+
gateway_name=gateway_name,
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
if contract.exchange == Exchange.CZCE:
|
|
587
|
+
contract.option_portfolio = data["ProductID"][:-1]
|
|
588
|
+
else:
|
|
589
|
+
contract.option_portfolio = data["ProductID"]
|
|
590
|
+
|
|
591
|
+
symbol_limit_map[contract.vt_symbol] = (data["UpStopPrice"], data["DownStopPrice"])
|
|
592
|
+
|
|
593
|
+
return contract
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vnpy_xtdata
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Xtquant datafeed/pub interface.
|
|
5
|
+
Author-email: YQ Tsui <qianyun210603@hotmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: quant,quantitative,investment,trading,algotrading,data
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
15
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
16
|
+
Classifier: Natural Language :: Chinese (Simplified)
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: vnpy>=4.0.0
|
|
22
|
+
Requires-Dist: xtquant>=240920.1.2
|
|
23
|
+
Requires-Dist: filelock>=3
|
|
24
|
+
Requires-Dist: pandas>=2
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: ruff; extra == "dev"
|
|
27
|
+
Requires-Dist: setuptools_scm>=8; extra == "dev"
|
|
28
|
+
Requires-Dist: setuptools>=64; extra == "dev"
|
|
29
|
+
Requires-Dist: wheel; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
<h1 align="center">VeighNa框架的迅投数据服务接口</h1>
|
|
33
|
+
|
|
34
|
+
***
|
|
35
|
+
|
|
36
|
+
<p align="center">
|
|
37
|
+
<img src ="https://img.shields.io/badge/version-1.0.0-blueviolet.svg"/>
|
|
38
|
+
<img src ="https://img.shields.io/badge/platform-windows-yellow.svg"/>
|
|
39
|
+
<img src ="https://img.shields.io/badge/python-3.11|3.12|3.13-blue.svg" />
|
|
40
|
+
<img src ="https://img.shields.io/github/license/vnpy/vnpy.svg?color=orange"/>
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
## 说明
|
|
44
|
+
|
|
45
|
+
基于迅投XtQuant封装开发的实时行情和数据服务接口,支持以下中国金融市场的K线和Tick数据:
|
|
46
|
+
|
|
47
|
+
* 股票、基金、债券、ETF期权:
|
|
48
|
+
* SSE:上海证券交易所
|
|
49
|
+
* SZSE:深圳证券交易所
|
|
50
|
+
* 期货、期货期权:
|
|
51
|
+
* CFFEX:中国金融期货交易所
|
|
52
|
+
* SHFE:上海期货交易所
|
|
53
|
+
* DCE:大连商品交易所
|
|
54
|
+
* CZCE:郑州商品交易所
|
|
55
|
+
* INE:上海国际能源交易中心
|
|
56
|
+
* GFEX:广州期货交易所
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
## 安装
|
|
60
|
+
|
|
61
|
+
安装环境推荐基于4.0.0版本以上的【[**VeighNa Studio**](https://www.vnpy.com/)】。
|
|
62
|
+
|
|
63
|
+
直接使用pip命令:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
pip install vnpy_xtdata
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
或者下载解压后在cmd中运行:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
pip install .
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 使用
|
|
77
|
+
|
|
78
|
+
**Token连接**
|
|
79
|
+
|
|
80
|
+
1. 连接前请先确保xtquant模块可以正常加载(在[投研知识库](http://docs.thinktrader.net/)下载xtquant的安装包,解压后放置xtquant包到自己使用的Python环境的site_packages文件夹下)。
|
|
81
|
+
2. 登录[迅投研服务平台](https://xuntou.net/#/userInfo),在【用户中心】-【个人设置】-【接口TOKEN】处获取Token。
|
|
82
|
+
3. 在VeighNa Trader的【全局配置】处进行数据服务配置:
|
|
83
|
+
* datafeed.name:xt
|
|
84
|
+
* datafeed.username:token
|
|
85
|
+
* datafeed.password:填复制的Token
|
|
86
|
+
|
|
87
|
+
**客户端连接**
|
|
88
|
+
|
|
89
|
+
1. 连接请先登录迅投极速交易终端,同时确保xtquant模块可以正常加载(点击【下载Python库】-【Python库下载】,下载完成后拷贝“Python库路径”下Lib\site-packages文件夹中的xtquant包到自己使用的Python环境的site_packages文件夹下)。
|
|
90
|
+
2. 在Veighna Trader的【全局配置】处进行数据服务配置:
|
|
91
|
+
* datafeed.name:xt
|
|
92
|
+
* datafeed.username:client
|
|
93
|
+
* datafeed.password:留空
|
|
94
|
+
3. 请注意以客户端方式连接时,需要保持迅投客户端的运行。
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
.gitignore
|
|
2
|
+
CHANGELOG.md
|
|
3
|
+
LICENSE
|
|
4
|
+
README.md
|
|
5
|
+
pyproject.toml
|
|
6
|
+
setup.py
|
|
7
|
+
vnpy_xtdata/__init__.py
|
|
8
|
+
vnpy_xtdata/_version.py
|
|
9
|
+
vnpy_xtdata/xt_datafeed.py
|
|
10
|
+
vnpy_xtdata/xt_datapub.py
|
|
11
|
+
vnpy_xtdata.egg-info/PKG-INFO
|
|
12
|
+
vnpy_xtdata.egg-info/SOURCES.txt
|
|
13
|
+
vnpy_xtdata.egg-info/dependency_links.txt
|
|
14
|
+
vnpy_xtdata.egg-info/requires.txt
|
|
15
|
+
vnpy_xtdata.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|