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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,10 @@
1
+ vnpy>=4.0.0
2
+ xtquant>=240920.1.2
3
+ filelock>=3
4
+ pandas>=2
5
+
6
+ [dev]
7
+ ruff
8
+ setuptools_scm>=8
9
+ setuptools>=64
10
+ wheel
@@ -0,0 +1,3 @@
1
+ build
2
+ dist
3
+ vnpy_xtdata