├── .github
├── PULL_REQUEST_TEMPLATE.md
├── SUPPORT.md
├── CODE_OF_CONDUCT.md
├── ISSUE_TEMPLATE.md
└── workflows
│ └── pythonapp.yml
├── .flake8
├── CHANGELOG.md
├── LICENSE
├── vnpy_tushare
├── __init__.py
└── tushare_datafeed.py
├── tests
└── test_tushare_datafeed.py
├── README.md
├── .gitignore
└── pyproject.toml
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | 建议每次发起的PR内容尽可能精简,复杂的修改请拆分为多次PR,便于管理合并。
2 |
3 | ## 改进内容
4 |
5 | 1.
6 | 2.
7 | 3.
8 |
9 | ## 相关的Issue号(如有)
10 |
11 | Close #
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | exclude = venv,build,__pycache__,__init__.py,ib,talib,uic
3 | ignore =
4 | E501 line too long, fixed by black
5 | W503 line break before binary operator
6 |
--------------------------------------------------------------------------------
/.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/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # 行为准则
2 |
3 | 这是一份VeighNa项目社区的行为准则,也是项目作者自己在刚入行量化金融行业时对于理想中的社区的期望:
4 |
5 | * 为交易员而生:作为一款从金融机构量化业务中诞生的交易系统开发框架,设计上都优先满足机构专业交易员的使用习惯,而不是其他用户(散户、爱好者、技术人员等)
6 |
7 | * 对新用户友好,保持耐心:大部分人在接触新东西的时候都是磕磕碰碰、有很多的问题,请记住此时别人对你伸出的援助之手,并把它传递给未来需要的人
8 |
9 | * 尊重他人,慎重言行:礼貌文明的交流方式除了能得到别人同样的回应,更能减少不必要的摩擦,保证高效的交流
10 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 1.4.21.0版本
2 |
3 | 1. vnpy框架4.0版本升级适配
4 |
5 | # 1.4.7.0版本
6 |
7 | 1. 增加单元测试用例
8 | 2. to_ts_asset函数增加ETF基金支持
9 |
10 | # 1.2.89.0版本
11 | 1. 增加output函数用于输出日志
12 |
13 | # 1.2.85.1版本
14 | 1. 修复郑商所合约数据下载失败的问题
15 |
16 | # 1.2.85.0版本
17 | 1. 使用zoneinfo替换pytz实现时区功能
18 |
19 | # 1.2.64.3版本
20 |
21 | 1. 优化对查询数据失败的容错支持
22 |
--------------------------------------------------------------------------------
/.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/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_tushare
29 | - name: Build packages with uv
30 | run: |
31 | # Build source distribution and wheel distribution
32 | uv build
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015-present, Xiaoyou Chen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/vnpy_tushare/__init__.py:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 | #
3 | # Copyright (c) 2015-present, Xiaoyou Chen
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in all
13 | # copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 |
23 |
24 | from .tushare_datafeed import TushareDatafeed as Datafeed
25 |
26 |
27 | __all__ = ["Datafeed"]
28 |
29 |
30 | __version__ = "1.4.21.0"
31 |
--------------------------------------------------------------------------------
/tests/test_tushare_datafeed.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from vnpy_tushare.tushare_datafeed import to_ts_asset
4 | from vnpy.trader.constant import Exchange
5 |
6 |
7 | class TestToTsAsset(unittest.TestCase):
8 | # 股票
9 | def test_stock(self) -> None:
10 | self.assertEqual(to_ts_asset('600009', Exchange.SSE), "E") # 沪市
11 | self.assertEqual(to_ts_asset('688981', Exchange.SSE), "E") # 科创版
12 | self.assertEqual(to_ts_asset('000001', Exchange.SZSE), "E") # 深市
13 | self.assertEqual(to_ts_asset('300308', Exchange.SZSE), "E") # 创业板
14 | self.assertEqual(to_ts_asset('430418', Exchange.BSE), "E") # 北交所
15 | self.assertEqual(to_ts_asset('835305', Exchange.BSE), "E") # 北交所
16 |
17 | # 指数
18 | def test_index(self) -> None:
19 | self.assertEqual(to_ts_asset('000001', Exchange.SSE), "I") # 上证指数
20 | self.assertEqual(to_ts_asset('000688', Exchange.SSE), "I") # 科创版指
21 | self.assertEqual(to_ts_asset('399001', Exchange.SZSE), "I") # 深证指数
22 | self.assertEqual(to_ts_asset('399006', Exchange.SZSE), "I") # 创业板指
23 | self.assertEqual(to_ts_asset('899050', Exchange.BSE), "I") # 北交所50
24 |
25 | # 基金
26 | def test_fund(self) -> None:
27 | self.assertEqual(to_ts_asset('159934', Exchange.SZSE), "FD") # 深市etf
28 | self.assertEqual(to_ts_asset('518880', Exchange.SSE), "FD") # 沪市etf
29 |
30 | # 期货
31 | def test_future(self) -> None:
32 | self.assertEqual(to_ts_asset('i2409', Exchange.CFFEX), "FT")
33 |
34 |
35 | if __name__ == '__main__':
36 | unittest.main()
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # VeighNa框架的TuShare数据服务接口
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | ## 说明
15 |
16 | 基于tushare模块的1.4.21版本开发,支持以下中国金融市场的K线数据:
17 |
18 | * 期货:
19 | * CFFEX:中国金融期货交易所
20 | * SHFE:上海期货交易所
21 | * DCE:大连商品交易所
22 | * CZCE:郑州商品交易所
23 | * INE:上海国际能源交易中心
24 | * 股票:
25 | * SSE:上海证券交易所
26 | * SZSE:深圳证券交易所
27 | * BSE:北京证券交易所
28 |
29 | 注意:需要使用相应的数据服务权限,可以通过[该页面](https://www.tushare.pro)注册使用。
30 |
31 | ## 数据使用事项
32 |
33 | tushare数据源期货数据中,第一条夜盘k线数据是集合竞价数据,用户可以根据自己需求进行过滤或者合并。
34 |
35 | ## 安装
36 |
37 | 安装环境推荐基于4.0.0版本以上的【[**VeighNa Studio**](https://www.vnpy.com)】。
38 |
39 | 直接使用pip命令:
40 |
41 | ```
42 | pip install vnpy_tushare
43 | ```
44 |
45 |
46 | 或者下载源代码后,解压后在cmd中运行:
47 |
48 | ```
49 | pip install .
50 | ```
51 |
52 |
53 | ## 使用
54 |
55 | 在VeighNa中使用TuShare时,需要在全局配置中填写以下字段信息:
56 |
57 | |名称|含义|必填|举例|
58 | |---------|----|---|---|
59 | |datafeed.name|名称|是|tushare|
60 | |datafeed.username|用户名|否|token|
61 | |datafeed.password|密码|是|c3a110417f08f26d2c221edc0c50d4a8a5001502eea89cf5|
62 |
63 |
64 | # 单元测试
65 |
66 | 单元测试代码目录为`./tests/test_*.py`
67 | ```sh
68 | # 指定文件执行单元测试
69 | python -m unittest tests\test_your_file_name.py
70 |
71 | # 全量执行
72 | python -m unittest
73 | ```
74 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "vnpy_tushare"
3 | dynamic = ["version"]
4 | description = "TuShare datafeed 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 :: Microsoft :: Windows",
12 | "Operating System :: POSIX :: Linux",
13 | "Operating System :: MacOS",
14 | "Programming Language :: Python :: 3",
15 | "Programming Language :: Python :: 3.10",
16 | "Programming Language :: Python :: 3.11",
17 | "Programming Language :: Python :: 3.12",
18 | "Programming Language :: Python :: 3.13",
19 | "Topic :: Office/Business :: Financial :: Investment",
20 | "Programming Language :: Python :: Implementation :: CPython",
21 | "Natural Language :: Chinese (Simplified)",
22 | "Typing :: Typed",
23 | ]
24 | requires-python = ">=3.10"
25 | dependencies = [
26 | "tushare>=1.4.21",
27 | ]
28 | keywords = ["quant", "quantitative", "investment", "trading", "algotrading"]
29 |
30 | [project.urls]
31 | "Homepage" = "https://www.vnpy.com"
32 | "Documentation" = "https://www.vnpy.com/docs"
33 | "Changes" = "https://github.com/vnpy/vnpy_tushare/blob/master/CHANGELOG.md"
34 | "Source" = "https://github.com/vnpy/vnpy_tushare/"
35 | "Forum" = "https://www.vnpy.com/forum"
36 |
37 | [build-system]
38 | requires = ["hatchling>=1.27.0"]
39 | build-backend = "hatchling.build"
40 |
41 | [tool.hatch.version]
42 | path = "vnpy_tushare/__init__.py"
43 | pattern = "__version__ = ['\"](?P[^'\"]+)['\"]"
44 |
45 | [tool.hatch.build.targets.wheel]
46 | packages = ["vnpy_tushare"]
47 | include-package-data = true
48 |
49 | [tool.hatch.build.targets.sdist]
50 | include = ["vnpy_tushare*"]
51 |
52 | [tool.ruff]
53 | target-version = "py310"
54 | output-format = "full"
55 |
56 | [tool.ruff.lint]
57 | select = [
58 | "B", # flake8-bugbear
59 | "E", # pycodestyle error
60 | "F", # pyflakes
61 | "UP", # pyupgrade
62 | "W", # pycodestyle warning
63 | ]
64 | ignore = ["E501"]
65 |
66 | [tool.mypy]
67 | python_version = "3.10"
68 | warn_return_any = true
69 | warn_unused_configs = true
70 | disallow_untyped_defs = true
71 | disallow_incomplete_defs = true
72 | check_untyped_defs = true
73 | disallow_untyped_decorators = true
74 | no_implicit_optional = true
75 | strict_optional = true
76 | warn_redundant_casts = true
77 | warn_unused_ignores = true
78 | warn_no_return = true
79 | ignore_missing_imports = true
80 |
--------------------------------------------------------------------------------
/vnpy_tushare/tushare_datafeed.py:
--------------------------------------------------------------------------------
1 | from datetime import timedelta, datetime
2 | from collections.abc import Callable
3 | from copy import deepcopy
4 | import re
5 |
6 | import pandas as pd
7 | from pandas import DataFrame
8 | import tushare as ts
9 | from tushare.pro.client import DataApi
10 |
11 | from vnpy.trader.setting import SETTINGS
12 | from vnpy.trader.datafeed import BaseDatafeed
13 | from vnpy.trader.constant import Exchange, Interval
14 | from vnpy.trader.object import BarData, HistoryRequest
15 | from vnpy.trader.utility import round_to, ZoneInfo
16 |
17 | # 数据频率映射
18 | INTERVAL_VT2TS: dict[Interval, str] = {
19 | Interval.MINUTE: "1min",
20 | Interval.HOUR: "60min",
21 | Interval.DAILY: "D",
22 | }
23 |
24 | # 股票支持列表
25 | STOCK_LIST: list[Exchange] = [
26 | Exchange.SSE,
27 | Exchange.SZSE,
28 | Exchange.BSE,
29 | ]
30 |
31 | # 期货支持列表
32 | FUTURE_LIST: list[Exchange] = [
33 | Exchange.CFFEX,
34 | Exchange.SHFE,
35 | Exchange.CZCE,
36 | Exchange.DCE,
37 | Exchange.INE,
38 | Exchange.GFEX
39 | ]
40 |
41 | # 交易所映射
42 | EXCHANGE_VT2TS: dict[Exchange, str] = {
43 | Exchange.CFFEX: "CFX",
44 | Exchange.SHFE: "SHF",
45 | Exchange.CZCE: "ZCE",
46 | Exchange.DCE: "DCE",
47 | Exchange.INE: "INE",
48 | Exchange.SSE: "SH",
49 | Exchange.SZSE: "SZ",
50 | Exchange.BSE: "BJ",
51 | Exchange.GFEX: "GFE"
52 | }
53 |
54 | # 时间调整映射
55 | INTERVAL_ADJUSTMENT_MAP: dict[Interval, timedelta] = {
56 | Interval.MINUTE: timedelta(minutes=1),
57 | Interval.HOUR: timedelta(hours=1),
58 | Interval.DAILY: timedelta()
59 | }
60 |
61 | # 中国上海时区
62 | CHINA_TZ = ZoneInfo("Asia/Shanghai")
63 |
64 |
65 | def to_ts_symbol(symbol: str, exchange: Exchange) -> str | None:
66 | """将交易所代码转换为tushare代码"""
67 | # 股票
68 | if exchange in STOCK_LIST:
69 | ts_symbol: str = f"{symbol}.{EXCHANGE_VT2TS[exchange]}"
70 | # 期货
71 | elif exchange in FUTURE_LIST:
72 | if exchange is not Exchange.CZCE:
73 | ts_symbol = f"{symbol}.{EXCHANGE_VT2TS[exchange]}".upper()
74 | else:
75 | for _count, word in enumerate(symbol):
76 | if word.isdigit():
77 | break
78 |
79 | year: str = symbol[_count]
80 | month: str = symbol[_count + 1:]
81 | if year == "9":
82 | year = "1" + year
83 | else:
84 | year = "2" + year
85 |
86 | product: str = symbol[:_count]
87 | ts_symbol = f"{product}{year}{month}.ZCE".upper()
88 | else:
89 | return None
90 |
91 | return ts_symbol
92 |
93 |
94 | def to_ts_asset(symbol: str, exchange: Exchange) -> str | None:
95 | """生成tushare资产类别"""
96 | # 股票
97 | if exchange in STOCK_LIST:
98 | if exchange is Exchange.SSE and symbol[0] == "6":
99 | asset: str = "E"
100 | elif exchange is Exchange.SSE and symbol[0] == "5":
101 | asset = "FD" # 场内etf
102 | elif exchange is Exchange.SZSE and symbol[0] == "1":
103 | asset = "FD" # 场内etf
104 | # 39开头是指数,比如399001
105 | elif exchange is Exchange.SZSE and re.search("^(0|3)", symbol) and not symbol.startswith('39'):
106 | asset= "E"
107 | # 89开头是指数,比如899050
108 | elif exchange is Exchange.BSE and not symbol.startswith('89'):
109 | asset = "E"
110 | else:
111 | asset = "I"
112 | # 期货
113 | elif exchange in FUTURE_LIST:
114 | asset = "FT"
115 | else:
116 | return None
117 |
118 | return asset
119 |
120 |
121 | class TushareDatafeed(BaseDatafeed):
122 | """TuShare数据服务接口"""
123 |
124 | def __init__(self) -> None:
125 | """"""
126 | self.username: str = SETTINGS["datafeed.username"]
127 | self.password: str = SETTINGS["datafeed.password"]
128 |
129 | self.inited: bool = False
130 |
131 | def init(self, output: Callable = print) -> bool:
132 | """初始化"""
133 | if self.inited:
134 | return True
135 |
136 | if not self.username:
137 | output("Tushare数据服务初始化失败:用户名为空!")
138 | return False
139 |
140 | if not self.password:
141 | output("Tushare数据服务初始化失败:密码为空!")
142 | return False
143 |
144 | ts.set_token(self.password)
145 | self.pro: DataApi | None = ts.pro_api()
146 | self.inited = True
147 |
148 | return True
149 |
150 | def query_bar_history(self, req: HistoryRequest, output: Callable = print) -> list[BarData] | None:
151 | """查询k线数据"""
152 | if not self.inited:
153 | self.init(output)
154 |
155 | symbol: str = req.symbol
156 | exchange: Exchange = req.exchange
157 | interval: Interval = req.interval
158 | start: datetime = req.start.strftime("%Y-%m-%d %H:%M:%S")
159 | end: datetime = req.end.strftime("%Y-%m-%d %H:%M:%S")
160 |
161 | ts_symbol: str | None = to_ts_symbol(symbol, exchange)
162 | if not ts_symbol:
163 | return None
164 |
165 | asset: str | None = to_ts_asset(symbol, exchange)
166 | if not asset:
167 | return None
168 |
169 | ts_interval: str | None = INTERVAL_VT2TS.get(interval)
170 | if not ts_interval:
171 | return None
172 |
173 | adjustment: timedelta = INTERVAL_ADJUSTMENT_MAP[interval]
174 |
175 | try:
176 | d1: DataFrame = ts.pro_bar(
177 | ts_code=ts_symbol,
178 | start_date=start,
179 | end_date=end,
180 | asset=asset,
181 | freq=ts_interval
182 | )
183 | except OSError as ex:
184 | output(f"发生输入/输出错误:{ex.strerror}")
185 | return []
186 |
187 | df: DataFrame = deepcopy(d1)
188 |
189 | while True:
190 | if len(d1) != 8000:
191 | break
192 | tmp_end: str = d1["trade_time"].values[-1]
193 |
194 | d1 = ts.pro_bar(
195 | ts_code=ts_symbol,
196 | start_date=start,
197 | end_date=tmp_end,
198 | asset=asset,
199 | freq=ts_interval
200 | )
201 | df = pd.concat([df[:-1], d1])
202 |
203 | bar_keys: list[datetime] = []
204 | bar_dict: dict[datetime, BarData] = {}
205 | data: list[BarData] = []
206 |
207 | # 处理原始数据中的NaN值
208 | df.fillna(0, inplace=True)
209 |
210 | if df is not None:
211 | for _ix, row in df.iterrows():
212 | if row["open"] is None:
213 | continue
214 |
215 | if interval.value == "d":
216 | dt_str: str = row["trade_date"]
217 | dt: datetime = datetime.strptime(dt_str, "%Y%m%d")
218 | else:
219 | dt_str = row["trade_time"]
220 | dt = datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S") - adjustment
221 |
222 | dt = dt.replace(tzinfo=CHINA_TZ)
223 |
224 | turnover = row.get("amount", 0)
225 | if turnover is None:
226 | turnover = 0
227 |
228 | open_interest = row.get("oi", 0)
229 | if open_interest is None:
230 | open_interest = 0
231 |
232 | bar: BarData = BarData(
233 | symbol=symbol,
234 | exchange=exchange,
235 | interval=interval,
236 | datetime=dt,
237 | open_price=round_to(row["open"], 0.000001),
238 | high_price=round_to(row["high"], 0.000001),
239 | low_price=round_to(row["low"], 0.000001),
240 | close_price=round_to(row["close"], 0.000001),
241 | volume=row["vol"],
242 | turnover=turnover,
243 | open_interest=open_interest,
244 | gateway_name="TS"
245 | )
246 |
247 | bar_dict[dt] = bar
248 |
249 | bar_keys = sorted(bar_dict.keys(), reverse=False)
250 | for i in bar_keys:
251 | data.append(bar_dict[i])
252 |
253 | return data
254 |
--------------------------------------------------------------------------------