kitetdx 0.1.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.
- kitetdx-0.1.0/LICENSE +10 -0
- kitetdx-0.1.0/PKG-INFO +102 -0
- kitetdx-0.1.0/README.md +67 -0
- kitetdx-0.1.0/kitetdx/__init__.py +5 -0
- kitetdx-0.1.0/kitetdx/affair.py +39 -0
- kitetdx-0.1.0/kitetdx/entities/__init__.py +27 -0
- kitetdx-0.1.0/kitetdx/quotes.py +169 -0
- kitetdx-0.1.0/kitetdx/reader.py +297 -0
- kitetdx-0.1.0/pyproject.toml +82 -0
kitetdx-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
|
|
2
|
+
MIT License
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2017, mootdx
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
7
|
+
|
|
8
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
9
|
+
|
|
10
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
kitetdx-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kitetdx
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A custom wrapper and extension for mootdx.
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: Kiteflyingee
|
|
8
|
+
Requires-Python: >=3.8,<4.0
|
|
9
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Natural Language :: English
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
23
|
+
Requires-Dist: click (>=8.1.3,<9.0.0)
|
|
24
|
+
Requires-Dist: httpx (>=0.25.0,<0.26.0)
|
|
25
|
+
Requires-Dist: mini-racer (>=0.12.0,<0.13.0)
|
|
26
|
+
Requires-Dist: prettytable (>=3.5.0,<4.0.0)
|
|
27
|
+
Requires-Dist: tdxpy (>=0.2.5,<0.3.0)
|
|
28
|
+
Requires-Dist: tenacity (>=8.1.0,<9.0.0)
|
|
29
|
+
Requires-Dist: tqdm
|
|
30
|
+
Requires-Dist: typing-extensions (>=4.5.0,<5.0.0)
|
|
31
|
+
Project-URL: Homepage, https://github.com/Kiteflyingee/kitetdx
|
|
32
|
+
Project-URL: Repository, https://github.com/Kiteflyingee/kitetdx
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# Kitetdx
|
|
36
|
+
|
|
37
|
+
**Kitetdx** 是一个基于 [mootdx](https://github.com/mootdx/mootdx) 的二次封装与扩展项目。它提供了一套统一且稳定的 API 用于访问金融数据,内置了定制化的 `Reader` 模块,并对 `Quotes` 进行了完整的封装。
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## 功能特性
|
|
41
|
+
|
|
42
|
+
- **定制化 Reader 模块**: 位于 `kitetdx.reader`,针对特定项目需求重写了数据读取逻辑(如概念板块解析),完全独立于 `mootdx` 的 reader 实现。
|
|
43
|
+
- **统一 API 接口**: 对 `Quotes` 等模块进行了显式封装,提供了完整的文档注释,确保用户代码与底层实现解耦。
|
|
44
|
+
- **可扩展架构**: 设计上允许未来替换底层实现(如从 `mootdx` 切换到 `tushare` 或自研协议),而无需修改用户侧代码。
|
|
45
|
+
|
|
46
|
+
## 安装指南
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install kitetdx
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 使用说明
|
|
53
|
+
|
|
54
|
+
### 离线数据读取 (定制实现)
|
|
55
|
+
|
|
56
|
+
`kitetdx` 的 `Reader` 模块提供了增强的离线数据读取功能。
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from kitetdx import Reader
|
|
60
|
+
|
|
61
|
+
# 初始化 Reader,指定通达信安装目录
|
|
62
|
+
reader = Reader.factory(market='std', tdxdir='/path/to/tdx')
|
|
63
|
+
|
|
64
|
+
# 读取日线数据
|
|
65
|
+
df = reader.daily(symbol='600036')
|
|
66
|
+
print(df)
|
|
67
|
+
|
|
68
|
+
# 读取板块数据 (定制逻辑)
|
|
69
|
+
concepts = reader.block()
|
|
70
|
+
for concept in concepts:
|
|
71
|
+
print(f"概念: {concept.concept_name}, 股票数: {len(concept.stocks)}")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 在线行情 (封装 Mootdx)
|
|
75
|
+
|
|
76
|
+
`Quotes` 模块封装了 `mootdx.quotes`,提供了一致的 API。
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from kitetdx import Quotes
|
|
80
|
+
|
|
81
|
+
# 初始化行情客户端
|
|
82
|
+
client = Quotes.factory(market='std', multithread=True, heartbeat=True)
|
|
83
|
+
|
|
84
|
+
# 获取实时 K 线
|
|
85
|
+
df = client.bars(symbol='600036', frequency=9, offset=10)
|
|
86
|
+
print(df)
|
|
87
|
+
|
|
88
|
+
# 获取实时分时
|
|
89
|
+
df = client.minute(symbol='000001')
|
|
90
|
+
print(df)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## 架构说明
|
|
94
|
+
|
|
95
|
+
- **`kitetdx.reader`**: 定制实现,不依赖 `mootdx.reader`。
|
|
96
|
+
- **`kitetdx.quotes`**: `mootdx.quotes` 的完整封装。
|
|
97
|
+
- **`kitetdx.entities`**: 统一的数据实体定义(如 `Stock`, `Concept`)。
|
|
98
|
+
|
|
99
|
+
## 致谢
|
|
100
|
+
|
|
101
|
+
本项目基于 [mootdx](https://github.com/mootdx/mootdx) 构建,感谢原作者的卓越工作。
|
|
102
|
+
|
kitetdx-0.1.0/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Kitetdx
|
|
2
|
+
|
|
3
|
+
**Kitetdx** 是一个基于 [mootdx](https://github.com/mootdx/mootdx) 的二次封装与扩展项目。它提供了一套统一且稳定的 API 用于访问金融数据,内置了定制化的 `Reader` 模块,并对 `Quotes` 进行了完整的封装。
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## 功能特性
|
|
7
|
+
|
|
8
|
+
- **定制化 Reader 模块**: 位于 `kitetdx.reader`,针对特定项目需求重写了数据读取逻辑(如概念板块解析),完全独立于 `mootdx` 的 reader 实现。
|
|
9
|
+
- **统一 API 接口**: 对 `Quotes` 等模块进行了显式封装,提供了完整的文档注释,确保用户代码与底层实现解耦。
|
|
10
|
+
- **可扩展架构**: 设计上允许未来替换底层实现(如从 `mootdx` 切换到 `tushare` 或自研协议),而无需修改用户侧代码。
|
|
11
|
+
|
|
12
|
+
## 安装指南
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install kitetdx
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 使用说明
|
|
19
|
+
|
|
20
|
+
### 离线数据读取 (定制实现)
|
|
21
|
+
|
|
22
|
+
`kitetdx` 的 `Reader` 模块提供了增强的离线数据读取功能。
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from kitetdx import Reader
|
|
26
|
+
|
|
27
|
+
# 初始化 Reader,指定通达信安装目录
|
|
28
|
+
reader = Reader.factory(market='std', tdxdir='/path/to/tdx')
|
|
29
|
+
|
|
30
|
+
# 读取日线数据
|
|
31
|
+
df = reader.daily(symbol='600036')
|
|
32
|
+
print(df)
|
|
33
|
+
|
|
34
|
+
# 读取板块数据 (定制逻辑)
|
|
35
|
+
concepts = reader.block()
|
|
36
|
+
for concept in concepts:
|
|
37
|
+
print(f"概念: {concept.concept_name}, 股票数: {len(concept.stocks)}")
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 在线行情 (封装 Mootdx)
|
|
41
|
+
|
|
42
|
+
`Quotes` 模块封装了 `mootdx.quotes`,提供了一致的 API。
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from kitetdx import Quotes
|
|
46
|
+
|
|
47
|
+
# 初始化行情客户端
|
|
48
|
+
client = Quotes.factory(market='std', multithread=True, heartbeat=True)
|
|
49
|
+
|
|
50
|
+
# 获取实时 K 线
|
|
51
|
+
df = client.bars(symbol='600036', frequency=9, offset=10)
|
|
52
|
+
print(df)
|
|
53
|
+
|
|
54
|
+
# 获取实时分时
|
|
55
|
+
df = client.minute(symbol='000001')
|
|
56
|
+
print(df)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 架构说明
|
|
60
|
+
|
|
61
|
+
- **`kitetdx.reader`**: 定制实现,不依赖 `mootdx.reader`。
|
|
62
|
+
- **`kitetdx.quotes`**: `mootdx.quotes` 的完整封装。
|
|
63
|
+
- **`kitetdx.entities`**: 统一的数据实体定义(如 `Stock`, `Concept`)。
|
|
64
|
+
|
|
65
|
+
## 致谢
|
|
66
|
+
|
|
67
|
+
本项目基于 [mootdx](https://github.com/mootdx/mootdx) 构建,感谢原作者的卓越工作。
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from mootdx.affair import Affair as MooAffair
|
|
2
|
+
|
|
3
|
+
class Affair(object):
|
|
4
|
+
"""
|
|
5
|
+
Kitetdx Affair Module
|
|
6
|
+
|
|
7
|
+
Wraps mootdx.affair.Affair to provide financial data access.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
@staticmethod
|
|
11
|
+
def files():
|
|
12
|
+
"""
|
|
13
|
+
获取远程文件列表
|
|
14
|
+
|
|
15
|
+
:return: list
|
|
16
|
+
"""
|
|
17
|
+
return MooAffair.files()
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def fetch(downdir='tmp', filename=''):
|
|
21
|
+
"""
|
|
22
|
+
下载财务文件
|
|
23
|
+
|
|
24
|
+
:param downdir: 下载目录
|
|
25
|
+
:param filename: 文件名
|
|
26
|
+
:return: bool
|
|
27
|
+
"""
|
|
28
|
+
return MooAffair.fetch(downdir=downdir, filename=filename)
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def parse(downdir='tmp', filename=''):
|
|
32
|
+
"""
|
|
33
|
+
解析财务文件
|
|
34
|
+
|
|
35
|
+
:param downdir: 下载目录
|
|
36
|
+
:param filename: 文件名 (可选,如果不指定则解析目录下所有)
|
|
37
|
+
:return: pd.DataFrame or None
|
|
38
|
+
"""
|
|
39
|
+
return MooAffair.parse(downdir=downdir, filename=filename)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class Stock:
|
|
6
|
+
exchange: str # '0','1','2'
|
|
7
|
+
stock_code: str
|
|
8
|
+
stock_name: Optional[str] = None
|
|
9
|
+
|
|
10
|
+
@property
|
|
11
|
+
def full_code(self):
|
|
12
|
+
exchange_map = {'0': 'sz', '1': 'sh', '2': 'bj'}
|
|
13
|
+
return f"{exchange_map.get(self.exchange, '')}{self.stock_code}"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class Concept:
|
|
18
|
+
concept_type: str # 'GN','FG','ZS'
|
|
19
|
+
concept_name: str
|
|
20
|
+
concept_code: str
|
|
21
|
+
stocks: List[Stock]
|
|
22
|
+
|
|
23
|
+
def __init__(self, concept_type: str, concept_name: str, concept_code: str):
|
|
24
|
+
self.concept_type = concept_type
|
|
25
|
+
self.concept_name = concept_name
|
|
26
|
+
self.concept_code = concept_code
|
|
27
|
+
self.stocks = []
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
from mootdx.quotes import Quotes as MooQuotes
|
|
2
|
+
from mootdx.consts import MARKET_SH
|
|
3
|
+
|
|
4
|
+
class Quotes(object):
|
|
5
|
+
"""
|
|
6
|
+
Kitetdx Quotes Module
|
|
7
|
+
|
|
8
|
+
Wraps mootdx.quotes.Quotes to provide a unified and documented API.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def factory(market='std', **kwargs):
|
|
13
|
+
"""
|
|
14
|
+
Quotes Factory Method
|
|
15
|
+
|
|
16
|
+
:param market: std (Standard Market), ext (Extended Market)
|
|
17
|
+
:param kwargs: Variable arguments
|
|
18
|
+
:return: Quotes object
|
|
19
|
+
"""
|
|
20
|
+
return MooQuotes.factory(market=market, **kwargs)
|
|
21
|
+
|
|
22
|
+
def __init__(self, **kwargs):
|
|
23
|
+
self._client = MooQuotes.factory(**kwargs)
|
|
24
|
+
|
|
25
|
+
def bars(self, symbol='000001', frequency=9, start=0, offset=800, **kwargs):
|
|
26
|
+
"""
|
|
27
|
+
获取实时日K线数据
|
|
28
|
+
|
|
29
|
+
:param symbol: 股票代码
|
|
30
|
+
:param frequency: 数据频次 9=日线
|
|
31
|
+
:param start: 开始位置
|
|
32
|
+
:param offset: 每次获取条数
|
|
33
|
+
:return: pd.DataFrame or None
|
|
34
|
+
"""
|
|
35
|
+
return self._client.bars(symbol=symbol, frequency=frequency, start=start, offset=offset, **kwargs)
|
|
36
|
+
|
|
37
|
+
def index_bars(self, symbol='000001', frequency=9, start=0, offset=800, **kwargs):
|
|
38
|
+
"""
|
|
39
|
+
获取指数K线数据
|
|
40
|
+
|
|
41
|
+
:param symbol: 股票代码
|
|
42
|
+
:param frequency: 数据频次
|
|
43
|
+
:param start: 开始位置
|
|
44
|
+
:param offset: 获取数量
|
|
45
|
+
:return: pd.DataFrame or None
|
|
46
|
+
"""
|
|
47
|
+
return self._client.index_bars(symbol=symbol, frequency=frequency, start=start, offset=offset, **kwargs)
|
|
48
|
+
|
|
49
|
+
def minute(self, symbol=None, **kwargs):
|
|
50
|
+
"""
|
|
51
|
+
获取实时分时数据
|
|
52
|
+
|
|
53
|
+
:param symbol: 股票代码
|
|
54
|
+
:return: pd.DataFrame
|
|
55
|
+
"""
|
|
56
|
+
return self._client.minute(symbol=symbol, **kwargs)
|
|
57
|
+
|
|
58
|
+
def minutes(self, symbol=None, date='20191023', **kwargs):
|
|
59
|
+
"""
|
|
60
|
+
分时历史数据
|
|
61
|
+
|
|
62
|
+
:param symbol: 股票代码
|
|
63
|
+
:param date: 查询日期
|
|
64
|
+
:return: pd.DataFrame or None
|
|
65
|
+
"""
|
|
66
|
+
return self._client.minutes(symbol=symbol, date=date, **kwargs)
|
|
67
|
+
|
|
68
|
+
def transaction(self, symbol='', start=0, offset=800, **kwargs):
|
|
69
|
+
"""
|
|
70
|
+
查询分笔成交
|
|
71
|
+
|
|
72
|
+
:param symbol: 股票代码
|
|
73
|
+
:param start: 起始位置
|
|
74
|
+
:param offset: 结束位置
|
|
75
|
+
:return: pd.DataFrame or None
|
|
76
|
+
"""
|
|
77
|
+
return self._client.transaction(symbol=symbol, start=start, offset=offset, **kwargs)
|
|
78
|
+
|
|
79
|
+
def transactions(self, symbol='', start=0, offset=800, date='20170209', **kwargs):
|
|
80
|
+
"""
|
|
81
|
+
查询历史分笔成交
|
|
82
|
+
|
|
83
|
+
:param symbol: 股票代码
|
|
84
|
+
:param start: 起始位置
|
|
85
|
+
:param offset: 获取数量
|
|
86
|
+
:param date: 查询日期
|
|
87
|
+
:return: pd.DataFrame or None
|
|
88
|
+
"""
|
|
89
|
+
return self._client.transactions(symbol=symbol, start=start, offset=offset, date=date, **kwargs)
|
|
90
|
+
|
|
91
|
+
def F10(self, symbol='', name=''):
|
|
92
|
+
"""
|
|
93
|
+
读取公司信息详情
|
|
94
|
+
|
|
95
|
+
:param name: 公司 F10 标题
|
|
96
|
+
:param symbol: 股票代码
|
|
97
|
+
:return: pd.DataFrame or None
|
|
98
|
+
"""
|
|
99
|
+
return self._client.F10(symbol=symbol, name=name)
|
|
100
|
+
|
|
101
|
+
def finance(self, symbol='000001', **kwargs):
|
|
102
|
+
"""
|
|
103
|
+
读取财务信息
|
|
104
|
+
|
|
105
|
+
:param symbol: 股票代码
|
|
106
|
+
:return: pd.DataFrame
|
|
107
|
+
"""
|
|
108
|
+
return self._client.finance(symbol=symbol, **kwargs)
|
|
109
|
+
|
|
110
|
+
def k(self, symbol='', begin=None, end=None, **kwargs):
|
|
111
|
+
"""
|
|
112
|
+
读取k线信息
|
|
113
|
+
|
|
114
|
+
:param symbol: 股票代码
|
|
115
|
+
:param begin: 开始日期
|
|
116
|
+
:param end: 截止日期
|
|
117
|
+
:return: pd.DataFrame or None
|
|
118
|
+
"""
|
|
119
|
+
return self._client.k(symbol=symbol, begin=begin, end=end, **kwargs)
|
|
120
|
+
|
|
121
|
+
def ohlc(self, **kwargs):
|
|
122
|
+
"""
|
|
123
|
+
读取k线信息 (Alias for k)
|
|
124
|
+
"""
|
|
125
|
+
return self.k(**kwargs)
|
|
126
|
+
|
|
127
|
+
def block(self, tofile='block.dat', **kwargs):
|
|
128
|
+
"""
|
|
129
|
+
获取证券板块信息
|
|
130
|
+
|
|
131
|
+
:param tofile: 保存文件
|
|
132
|
+
:return: pd.DataFrame or None
|
|
133
|
+
"""
|
|
134
|
+
return self._client.block(tofile=tofile, **kwargs)
|
|
135
|
+
|
|
136
|
+
def stock_count(self, market=MARKET_SH):
|
|
137
|
+
"""
|
|
138
|
+
获取市场股票数量
|
|
139
|
+
|
|
140
|
+
:param market: 股票市场代码 sh 上海, sz 深圳
|
|
141
|
+
:return: int
|
|
142
|
+
"""
|
|
143
|
+
return self._client.stock_count(market=market)
|
|
144
|
+
|
|
145
|
+
def stocks(self, market=MARKET_SH):
|
|
146
|
+
"""
|
|
147
|
+
获取股票列表
|
|
148
|
+
|
|
149
|
+
:param market: 股票市场
|
|
150
|
+
:return: pd.DataFrame
|
|
151
|
+
"""
|
|
152
|
+
return self._client.stocks(market=market)
|
|
153
|
+
|
|
154
|
+
def stock_all(self):
|
|
155
|
+
"""
|
|
156
|
+
获取所有股票列表
|
|
157
|
+
|
|
158
|
+
:return: pd.DataFrame
|
|
159
|
+
"""
|
|
160
|
+
return self._client.stock_all()
|
|
161
|
+
|
|
162
|
+
def xdxr(self, symbol='', **kwargs):
|
|
163
|
+
"""
|
|
164
|
+
读取除权除息信息
|
|
165
|
+
|
|
166
|
+
:param symbol: 股票代码
|
|
167
|
+
:return: pd.DataFrame or None
|
|
168
|
+
"""
|
|
169
|
+
return self._client.xdxr(symbol=symbol, **kwargs)
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
from tdxpy.reader import TdxExHqDailyBarReader
|
|
8
|
+
from tdxpy.reader import TdxLCMinBarReader
|
|
9
|
+
from tdxpy.reader import TdxMinBarReader
|
|
10
|
+
|
|
11
|
+
from mootdx.contrib.compat import MooTdxDailyBarReader
|
|
12
|
+
from mootdx.utils import get_stock_market
|
|
13
|
+
# from mootdx.utils import read_data
|
|
14
|
+
from mootdx.utils import to_data
|
|
15
|
+
from mootdx.logger import logger
|
|
16
|
+
|
|
17
|
+
def read_data(file_path):
|
|
18
|
+
"""
|
|
19
|
+
读取文件内容
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
with open(file_path, 'r', encoding='gbk') as f:
|
|
23
|
+
return f.read().strip().split('\n')
|
|
24
|
+
except FileNotFoundError:
|
|
25
|
+
logger.error(f"错误: 文件 {file_path} 不存在")
|
|
26
|
+
return None
|
|
27
|
+
except Exception as e:
|
|
28
|
+
logger.error(f"读取文件时出错: {e}")
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
from kitetdx.entities import Stock, Concept
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Reader(object):
|
|
36
|
+
@staticmethod
|
|
37
|
+
def factory(market='std', **kwargs):
|
|
38
|
+
"""
|
|
39
|
+
Reader 工厂方法
|
|
40
|
+
|
|
41
|
+
:param market: std 标准市场, ext 扩展市场
|
|
42
|
+
:param kwargs: 可变参数
|
|
43
|
+
:return:
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
if market == 'ext':
|
|
47
|
+
return ExtReader(**kwargs)
|
|
48
|
+
|
|
49
|
+
return StdReader(**kwargs)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ReaderBase(ABC):
|
|
53
|
+
# 默认通达信安装目录
|
|
54
|
+
tdxdir = 'C:/new_tdx'
|
|
55
|
+
|
|
56
|
+
def __init__(self, tdxdir=None):
|
|
57
|
+
"""
|
|
58
|
+
构造函数
|
|
59
|
+
|
|
60
|
+
:param tdxdir: 通达信安装目录
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
if not Path(tdxdir).is_dir():
|
|
64
|
+
raise Exception('tdxdir 目录不存在')
|
|
65
|
+
|
|
66
|
+
self.tdxdir = tdxdir
|
|
67
|
+
|
|
68
|
+
def find_path(self, symbol=None, subdir='lday', suffix=None, **kwargs):
|
|
69
|
+
"""
|
|
70
|
+
自动匹配文件路径,辅助函数
|
|
71
|
+
|
|
72
|
+
:param symbol:
|
|
73
|
+
:param subdir:
|
|
74
|
+
:param suffix:
|
|
75
|
+
:return: pd.dataFrame or None
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
# 判断市场, 带#扩展市场
|
|
79
|
+
if '#' in symbol:
|
|
80
|
+
market = 'ds'
|
|
81
|
+
# 通达信特有的板块指数88****开头的日线数据放在 sh 文件夹下
|
|
82
|
+
elif symbol.startswith('88'):
|
|
83
|
+
market = 'sh'
|
|
84
|
+
else:
|
|
85
|
+
# 判断是sh还是sz
|
|
86
|
+
market = get_stock_market(symbol, True)
|
|
87
|
+
|
|
88
|
+
# 判断前缀(市场是sh和sz重置前缀)
|
|
89
|
+
if market.lower() in ['sh', 'sz', 'bj']:
|
|
90
|
+
symbol = market + symbol.lower().replace(market, '')
|
|
91
|
+
|
|
92
|
+
# 判断后缀
|
|
93
|
+
suffix = suffix if isinstance(suffix, list) else [suffix]
|
|
94
|
+
|
|
95
|
+
# 调试使用
|
|
96
|
+
if kwargs.get('debug'):
|
|
97
|
+
return market, symbol, suffix
|
|
98
|
+
|
|
99
|
+
# 遍历扩展名
|
|
100
|
+
for ex_ in suffix:
|
|
101
|
+
ex_ = ex_.strip('.')
|
|
102
|
+
vipdoc = Path(self.tdxdir) / 'vipdoc' / market / subdir / f'{symbol}.{ex_}'
|
|
103
|
+
|
|
104
|
+
if Path(vipdoc).exists():
|
|
105
|
+
return vipdoc
|
|
106
|
+
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class StdReader(ReaderBase):
|
|
111
|
+
"""股票市场"""
|
|
112
|
+
|
|
113
|
+
def daily(self, symbol=None, **kwargs):
|
|
114
|
+
"""
|
|
115
|
+
获取日线数据
|
|
116
|
+
|
|
117
|
+
:param symbol: 证券代码
|
|
118
|
+
:return: pd.dataFrame or None
|
|
119
|
+
"""
|
|
120
|
+
symbol = Path(symbol).stem
|
|
121
|
+
reader = MooTdxDailyBarReader()
|
|
122
|
+
vipdoc = self.find_path(symbol=symbol, subdir='lday', suffix='day')
|
|
123
|
+
|
|
124
|
+
result = reader.get_df(str(vipdoc)) if vipdoc else None
|
|
125
|
+
return to_data(result, symbol=symbol, **kwargs)
|
|
126
|
+
|
|
127
|
+
def minute(self, symbol=None, suffix=1, **kwargs): # noqa
|
|
128
|
+
"""
|
|
129
|
+
获取1, 5分钟线
|
|
130
|
+
|
|
131
|
+
:param suffix: 文件前缀
|
|
132
|
+
:param symbol: 证券代码
|
|
133
|
+
:return: pd.dataFrame or None
|
|
134
|
+
"""
|
|
135
|
+
symbol = Path(symbol).stem
|
|
136
|
+
subdir = 'fzline' if str(suffix) == '5' else 'minline'
|
|
137
|
+
suffix = ['lc5', '5'] if str(suffix) == '5' else ['lc1', '1']
|
|
138
|
+
symbol = self.find_path(symbol, subdir=subdir, suffix=suffix)
|
|
139
|
+
|
|
140
|
+
if symbol is not None:
|
|
141
|
+
reader = TdxMinBarReader() if 'lc' not in symbol.suffix else TdxLCMinBarReader()
|
|
142
|
+
return reader.get_df(str(symbol))
|
|
143
|
+
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
def fzline(self, symbol=None):
|
|
147
|
+
"""
|
|
148
|
+
分钟线数据
|
|
149
|
+
|
|
150
|
+
:param symbol: 自定义板块股票列表, 类型 list
|
|
151
|
+
:return: pd.dataFrame or Bool
|
|
152
|
+
"""
|
|
153
|
+
return self.minute(symbol, suffix=5)
|
|
154
|
+
|
|
155
|
+
def block_new(self, name: str = None, symbol: list = None, group=False, **kwargs):
|
|
156
|
+
"""
|
|
157
|
+
自定义板块数据操作
|
|
158
|
+
|
|
159
|
+
:param name: 自定义板块名称
|
|
160
|
+
:param symbol: 自定义板块股票列表, 类型 list
|
|
161
|
+
:param group:
|
|
162
|
+
:return: pd.dataFrame or Bool
|
|
163
|
+
"""
|
|
164
|
+
from mootdx.tools.customize import Customize
|
|
165
|
+
|
|
166
|
+
reader = Customize(tdxdir=self.tdxdir)
|
|
167
|
+
|
|
168
|
+
if symbol:
|
|
169
|
+
return reader.create(name=name, symbol=symbol, **kwargs)
|
|
170
|
+
|
|
171
|
+
return reader.search(name=name, group=group)
|
|
172
|
+
|
|
173
|
+
def block(self):
|
|
174
|
+
"""
|
|
175
|
+
获取板块数据
|
|
176
|
+
:return: List[Concept]
|
|
177
|
+
"""
|
|
178
|
+
return self.parse_concept_data()
|
|
179
|
+
|
|
180
|
+
def parse_stock_mapping(self, file_path):
|
|
181
|
+
"""
|
|
182
|
+
解析股票代码-名称映射文件
|
|
183
|
+
返回: {股票代码: 股票名称} 的字典
|
|
184
|
+
"""
|
|
185
|
+
stock_mapping = {}
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
lines = read_data(Path(self.tdxdir) / 'T0002' / 'hq_cache' / file_path)
|
|
189
|
+
if not lines:
|
|
190
|
+
return {}
|
|
191
|
+
|
|
192
|
+
for line_num, line in enumerate(lines):
|
|
193
|
+
line = line.strip()
|
|
194
|
+
if not line:
|
|
195
|
+
continue
|
|
196
|
+
|
|
197
|
+
# 解析格式: 000001|平安银行|平安保险,谢永林,冀光恒
|
|
198
|
+
parts = line.split('|')
|
|
199
|
+
|
|
200
|
+
if len(parts) < 2:
|
|
201
|
+
logger.warning(f"警告: 第{line_num}行格式不正确: {line}")
|
|
202
|
+
continue
|
|
203
|
+
|
|
204
|
+
stock_code = parts[0].strip()
|
|
205
|
+
stock_name = parts[1].strip()
|
|
206
|
+
|
|
207
|
+
stock_name = stock_name.replace(' ', '').replace(' ', '') # 全角和半角空格
|
|
208
|
+
stock_mapping[stock_code] = stock_name
|
|
209
|
+
return stock_mapping
|
|
210
|
+
|
|
211
|
+
except Exception as e:
|
|
212
|
+
logger.error(f"解析文件时出错: {e}")
|
|
213
|
+
return {}
|
|
214
|
+
|
|
215
|
+
def parse_concept_data(self) -> List[Concept]:
|
|
216
|
+
"""
|
|
217
|
+
解析原始数据格式
|
|
218
|
+
"""
|
|
219
|
+
stock_mapping = self.parse_stock_mapping('infoharbor_ex.code')
|
|
220
|
+
concepts = []
|
|
221
|
+
|
|
222
|
+
current_concept = None
|
|
223
|
+
current_stocks = []
|
|
224
|
+
|
|
225
|
+
# 读取 GN/FG/ZS
|
|
226
|
+
gn_lines = read_data(Path(self.tdxdir) / 'T0002' / 'hq_cache' / 'infoharbor_block.dat')
|
|
227
|
+
if gn_lines:
|
|
228
|
+
for line in gn_lines:
|
|
229
|
+
if line.startswith('#'):
|
|
230
|
+
if current_concept:
|
|
231
|
+
current_concept.stocks = current_stocks
|
|
232
|
+
concepts.append(current_concept)
|
|
233
|
+
current_stocks = []
|
|
234
|
+
|
|
235
|
+
parts = line.strip('#').split(',')
|
|
236
|
+
concept_info = parts[0].split('_')
|
|
237
|
+
|
|
238
|
+
current_concept = Concept(
|
|
239
|
+
concept_type=concept_info[0],
|
|
240
|
+
concept_name=concept_info[1],
|
|
241
|
+
concept_code=parts[2]
|
|
242
|
+
)
|
|
243
|
+
else:
|
|
244
|
+
stock_items = line.split(',')
|
|
245
|
+
for item in stock_items:
|
|
246
|
+
if item and '#' in item:
|
|
247
|
+
exchange, code = item.split('#')
|
|
248
|
+
current_stocks.append(Stock(exchange=exchange, stock_code=code, stock_name=stock_mapping.get(code)))
|
|
249
|
+
|
|
250
|
+
# 添加最后一个概念
|
|
251
|
+
if current_concept:
|
|
252
|
+
current_concept.stocks = current_stocks
|
|
253
|
+
concepts.append(current_concept)
|
|
254
|
+
|
|
255
|
+
return concepts
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class ExtReader(ReaderBase):
|
|
259
|
+
"""扩展市场读取"""
|
|
260
|
+
|
|
261
|
+
def __init__(self, tdxdir=None):
|
|
262
|
+
super(ExtReader, self).__init__(tdxdir)
|
|
263
|
+
self.reader = TdxExHqDailyBarReader()
|
|
264
|
+
|
|
265
|
+
def daily(self, symbol=None):
|
|
266
|
+
"""
|
|
267
|
+
获取扩展市场日线数据
|
|
268
|
+
|
|
269
|
+
:return: pd.dataFrame or None
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
vipdoc = self.find_path(symbol=symbol, subdir='lday', suffix='day')
|
|
273
|
+
return self.reader.get_df(str(vipdoc)) if vipdoc else None
|
|
274
|
+
|
|
275
|
+
def minute(self, symbol=None):
|
|
276
|
+
"""
|
|
277
|
+
获取扩展市场分钟线数据
|
|
278
|
+
|
|
279
|
+
:return: pd.dataFrame or None
|
|
280
|
+
"""
|
|
281
|
+
|
|
282
|
+
if not symbol:
|
|
283
|
+
return None
|
|
284
|
+
|
|
285
|
+
vipdoc = self.find_path(symbol=symbol, subdir='minline', suffix=['lc1', '1'])
|
|
286
|
+
return self.reader.get_df(str(vipdoc)) if vipdoc else None
|
|
287
|
+
|
|
288
|
+
def fzline(self, symbol=None):
|
|
289
|
+
"""
|
|
290
|
+
获取日线数据
|
|
291
|
+
|
|
292
|
+
:return: pd.dataFrame or None
|
|
293
|
+
"""
|
|
294
|
+
|
|
295
|
+
vipdoc = self.find_path(symbol=symbol, subdir='fzline', suffix='lc5')
|
|
296
|
+
return self.reader.get_df(str(vipdoc)) if symbol else None
|
|
297
|
+
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "kitetdx"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A custom wrapper and extension for mootdx."
|
|
5
|
+
authors = ["Kiteflyingee"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
homepage = "https://github.com/Kiteflyingee/kitetdx"
|
|
9
|
+
repository = "https://github.com/Kiteflyingee/kitetdx"
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 2 - Pre-Alpha",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"License :: OSI Approved :: MIT License",
|
|
14
|
+
"Natural Language :: English",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.6",
|
|
17
|
+
"Programming Language :: Python :: 3.7",
|
|
18
|
+
"Programming Language :: Python :: 3.8",
|
|
19
|
+
"Programming Language :: Python :: 3.9",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
[tool.poetry.scripts]
|
|
26
|
+
mootdx = "mootdx.__main__:entry"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
[tool.poetry.dependencies]
|
|
30
|
+
python = "^3.8"
|
|
31
|
+
httpx = "^0.25.0"
|
|
32
|
+
tenacity = "^8.1.0"
|
|
33
|
+
tdxpy = "^0.2.5"
|
|
34
|
+
tqdm = "*"
|
|
35
|
+
prettytable = "^3.5.0"
|
|
36
|
+
click = "^8.1.3"
|
|
37
|
+
typing-extensions = "^4.5.0"
|
|
38
|
+
mini-racer = "^0.12.0"
|
|
39
|
+
|
|
40
|
+
[tool.poetry.group.test.dependencies]
|
|
41
|
+
pytest-cov = "^4.0.0"
|
|
42
|
+
freezegun = "^1.2.2"
|
|
43
|
+
pytest = "^7.3.1"
|
|
44
|
+
|
|
45
|
+
[build-system]
|
|
46
|
+
requires = ["poetry-core"]
|
|
47
|
+
build-backend = "poetry.core.masonry.api"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
[tool.pytest.ini_options]
|
|
51
|
+
testpaths = "tests"
|
|
52
|
+
addopts = "-p no:warnings"
|
|
53
|
+
log_cli = 0
|
|
54
|
+
log_cli_level = "DEBUG"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
[tool.commitizen]
|
|
58
|
+
tag_format = "v$version"
|
|
59
|
+
annotated_tag = true
|
|
60
|
+
changelog_file = "docs/history.md"
|
|
61
|
+
changelog_incremental = true
|
|
62
|
+
update_changelog_on_bump = true
|
|
63
|
+
version_provider = "poetry"
|
|
64
|
+
version_files = [
|
|
65
|
+
"mootdx/__init__.py:__version__",
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
[tool.poe.tasks]
|
|
70
|
+
venv = "poetry install --sync"
|
|
71
|
+
lock = "poetry lock --no-update"
|
|
72
|
+
|
|
73
|
+
test = "poetry run pytest"
|
|
74
|
+
lint = "ruff check ./mootdx/*.py"
|
|
75
|
+
|
|
76
|
+
dist = "poetry build -v"
|
|
77
|
+
bump = "cz bump --yes -ch -cc --increment"
|
|
78
|
+
|
|
79
|
+
sync = "make sync"
|
|
80
|
+
pypi = "poetry publish --build --skip-existing"
|
|
81
|
+
|
|
82
|
+
#{MAJOR,MINOR,PATCH}
|