kitetdx 0.1.6__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.6/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.6/PKG-INFO ADDED
@@ -0,0 +1,101 @@
1
+ Metadata-Version: 2.4
2
+ Name: kitetdx
3
+ Version: 0.1.6
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
+ Requires-Dist: click (>=8.1.3,<9.0.0)
22
+ Requires-Dist: httpx (>=0.25.0,<0.26.0)
23
+ Requires-Dist: mini-racer (>=0.12.0,<0.13.0)
24
+ Requires-Dist: prettytable (>=3.5.0,<4.0.0)
25
+ Requires-Dist: selenium (>=4.15.0,<5.0.0)
26
+ Requires-Dist: tdxpy (>=0.2.5,<0.3.0)
27
+ Requires-Dist: tenacity (>=8.1.0,<9.0.0)
28
+ Requires-Dist: tqdm
29
+ Requires-Dist: typing-extensions (>=4.5.0,<5.0.0)
30
+ Requires-Dist: webdriver-manager (>=4.0.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
+ - [API 参考](docs/api.md)
96
+ - [使用指南](docs/guide.md)
97
+
98
+ ## 致谢
99
+
100
+ 本项目基于 [mootdx](https://github.com/mootdx/mootdx) 构建,感谢原作者的卓越工作。
101
+
@@ -0,0 +1,66 @@
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
+ - [API 参考](docs/api.md)
62
+ - [使用指南](docs/guide.md)
63
+
64
+ ## 致谢
65
+
66
+ 本项目基于 [mootdx](https://github.com/mootdx/mootdx) 构建,感谢原作者的卓越工作。
@@ -0,0 +1,6 @@
1
+ from .quotes import Quotes
2
+ from .reader import Reader
3
+ from .affair import Affair
4
+ from .adjust import adjust_price, to_adjust, fetch_fq_factor
5
+
6
+ __all__ = ['Quotes', 'Reader', 'Affair', 'adjust_price', 'to_adjust', 'fetch_fq_factor']
@@ -0,0 +1,289 @@
1
+ # @Author : kitetdx
2
+ # @Time : 2024
3
+ # @Function: 复权因子获取和复权计算
4
+
5
+ import json
6
+ import datetime
7
+ import time
8
+ from pathlib import Path
9
+ from typing import Optional
10
+
11
+ import pandas as pd
12
+ import urllib.request
13
+
14
+ from mootdx.logger import logger
15
+
16
+
17
+ # 新浪复权因子接口
18
+ ZH_SINA_HFQ_URL = 'https://finance.sina.com.cn/realstock/company/{}/hfq.js'
19
+ ZH_SINA_QFQ_URL = 'https://finance.sina.com.cn/realstock/company/{}/qfq.js'
20
+
21
+ # 缓存目录和过期时间(一周 = 7天)
22
+ CACHE_DIR = Path.home() / '.kitetdx' / 'fq_cache'
23
+ CACHE_EXPIRE_DAYS = 7
24
+
25
+
26
+ def _get_sina_symbol(symbol: str) -> str:
27
+ """
28
+ 将股票代码转换为新浪格式
29
+
30
+ Args:
31
+ symbol: 股票代码,如 '000001' 或 '600000'
32
+
33
+ Returns:
34
+ 新浪格式的股票代码,如 'sz000001' 或 'sh600000'
35
+ """
36
+ symbol = str(symbol).strip()
37
+
38
+ # 如果已经有前缀,直接返回
39
+ if symbol.startswith(('sh', 'sz', 'SH', 'SZ')):
40
+ return symbol.lower()
41
+
42
+ # 根据股票代码判断市场
43
+ if symbol.startswith(('6', '9', '5')):
44
+ return f'sh{symbol}'
45
+ else:
46
+ return f'sz{symbol}'
47
+
48
+
49
+ def _get_cache_path(symbol: str, method: str) -> Path:
50
+ """获取缓存文件路径"""
51
+ sina_symbol = _get_sina_symbol(symbol)
52
+ return CACHE_DIR / f'{sina_symbol}_{method}.json'
53
+
54
+
55
+ def _is_cache_valid(cache_path: Path) -> bool:
56
+ """检查缓存是否有效(一周内)"""
57
+ if not cache_path.exists():
58
+ return False
59
+
60
+ try:
61
+ mtime = cache_path.stat().st_mtime
62
+ cache_age_days = (time.time() - mtime) / (24 * 3600)
63
+ return cache_age_days < CACHE_EXPIRE_DAYS
64
+ except Exception:
65
+ return False
66
+
67
+
68
+ def _load_cache(cache_path: Path) -> Optional[pd.DataFrame]:
69
+ """从缓存加载复权因子"""
70
+ try:
71
+ with open(cache_path, 'r', encoding='utf-8') as f:
72
+ data = json.load(f)
73
+
74
+ if not data.get('data'):
75
+ return None
76
+
77
+ df = pd.DataFrame(data['data'])
78
+ df.columns = ['date', 'factor']
79
+ df['date'] = pd.to_datetime(df['date'])
80
+ df['factor'] = df['factor'].astype(float)
81
+ df = df.set_index('date')
82
+ return df
83
+ except Exception as e:
84
+ logger.warning(f"加载缓存失败: {e}")
85
+ return None
86
+
87
+
88
+ def _save_cache(cache_path: Path, data: list):
89
+ """保存复权因子到缓存"""
90
+ try:
91
+ CACHE_DIR.mkdir(parents=True, exist_ok=True)
92
+ with open(cache_path, 'w', encoding='utf-8') as f:
93
+ json.dump({'data': data, 'update_time': time.time()}, f)
94
+ except Exception as e:
95
+ logger.warning(f"保存缓存失败: {e}")
96
+
97
+
98
+ def fetch_fq_factor(symbol: str, method: str = 'qfq', timeout: int = 10) -> Optional[pd.DataFrame]:
99
+ """
100
+ 从新浪获取复权因子(带缓存,一周内不重复请求)
101
+
102
+ Args:
103
+ symbol: 股票代码
104
+ method: 复权方式,'qfq' 前复权,'hfq' 后复权
105
+ timeout: 请求超时时间(秒)
106
+
107
+ Returns:
108
+ 复权因子DataFrame,包含 date 和 factor 列
109
+ """
110
+ # 检查缓存
111
+ cache_path = _get_cache_path(symbol, method)
112
+ if _is_cache_valid(cache_path):
113
+ logger.debug(f"使用缓存的复权因子: {symbol} {method}")
114
+ cached_df = _load_cache(cache_path)
115
+ if cached_df is not None:
116
+ return cached_df
117
+
118
+ # 缓存无效,从网络获取
119
+ sina_symbol = _get_sina_symbol(symbol)
120
+
121
+ if method == 'hfq':
122
+ url = ZH_SINA_HFQ_URL.format(sina_symbol)
123
+ else:
124
+ url = ZH_SINA_QFQ_URL.format(sina_symbol)
125
+
126
+ try:
127
+ req = urllib.request.Request(
128
+ url,
129
+ headers={'User-Agent': 'Mozilla/5.0'}
130
+ )
131
+
132
+ with urllib.request.urlopen(req, timeout=timeout) as response:
133
+ text = response.read().decode('utf-8')
134
+
135
+ # 解析返回的JS格式数据
136
+ # 格式: var _sh600000qfq={"total":30,"data":[...]};\n/* 注释 */
137
+ json_str = text.split('=')[1].strip()
138
+
139
+ # 去掉末尾的分号
140
+ if json_str.endswith(';'):
141
+ json_str = json_str[:-1]
142
+
143
+ # 去掉末尾的JS注释 /* ... */
144
+ if '/*' in json_str:
145
+ json_str = json_str[:json_str.index('/*')].strip()
146
+
147
+ # 去掉可能的换行符
148
+ json_str = json_str.strip()
149
+
150
+ data = json.loads(json_str)
151
+
152
+ if not data.get('data'):
153
+ logger.warning(f"获取 {symbol} {method} 复权因子为空")
154
+ return None
155
+
156
+ # 保存到缓存
157
+ _save_cache(cache_path, data['data'])
158
+ logger.debug(f"已缓存复权因子: {symbol} {method}")
159
+
160
+ df = pd.DataFrame(data['data'])
161
+ df.columns = ['date', 'factor']
162
+ df['date'] = pd.to_datetime(df['date'])
163
+ df['factor'] = df['factor'].astype(float)
164
+ df = df.set_index('date')
165
+
166
+ return df
167
+
168
+ except Exception as e:
169
+ logger.error(f"获取 {symbol} {method} 复权因子失败: {e}")
170
+ # 网络失败时尝试使用过期缓存
171
+ if cache_path.exists():
172
+ logger.info(f"网络失败,尝试使用过期缓存: {symbol}")
173
+ return _load_cache(cache_path)
174
+ return None
175
+
176
+
177
+
178
+ def adjust_price(df: pd.DataFrame, symbol: str, method: str = 'qfq') -> pd.DataFrame:
179
+ """
180
+ 对股票数据进行复权处理
181
+
182
+ Args:
183
+ df: 原始股票数据,需要包含 date, open, high, low, close 列
184
+ symbol: 股票代码
185
+ method: 复权方式,'qfq' 前复权,'hfq' 后复权,None 或其他值不复权
186
+
187
+ Returns:
188
+ 复权后的DataFrame
189
+ """
190
+ if method not in ('qfq', 'hfq'):
191
+ return df
192
+
193
+ if df is None or df.empty:
194
+ return df
195
+
196
+ # 获取复权因子
197
+ factor_df = fetch_fq_factor(symbol, method)
198
+
199
+ if factor_df is None or factor_df.empty:
200
+ logger.warning(f"无法获取 {symbol} 的复权因子,返回原始数据")
201
+ return df
202
+
203
+ # 确保df有date索引
204
+ df_copy = df.copy()
205
+
206
+ # 检查是否需要设置date索引
207
+ if 'date' in df_copy.columns and not isinstance(df_copy.index, pd.DatetimeIndex):
208
+ df_copy['date'] = pd.to_datetime(df_copy['date'])
209
+ df_copy = df_copy.set_index('date')
210
+ elif isinstance(df_copy.index, pd.DatetimeIndex):
211
+ pass
212
+ else:
213
+ # 尝试从year, month, day列构建date
214
+ if all(col in df_copy.columns for col in ['year', 'month', 'day']):
215
+ df_copy['date'] = pd.to_datetime(df_copy[['year', 'month', 'day']])
216
+ df_copy = df_copy.set_index('date')
217
+ else:
218
+ logger.warning("数据中没有日期信息,无法进行复权")
219
+ return df
220
+
221
+ # 合并复权因子
222
+ df_copy = df_copy.sort_index()
223
+ factor_df = factor_df.sort_index()
224
+
225
+ # 只取数据范围内的因子
226
+ start_date = df_copy.index.min()
227
+ end_date = df_copy.index.max()
228
+
229
+ df_merged = pd.merge(
230
+ df_copy,
231
+ factor_df,
232
+ left_index=True,
233
+ right_index=True,
234
+ how='left'
235
+ )
236
+
237
+ # 填充缺失的因子
238
+ if method == 'qfq':
239
+ # 前复权:向后填充
240
+ df_merged['factor'] = df_merged['factor'].bfill()
241
+ else:
242
+ # 后复权:向前填充
243
+ df_merged['factor'] = df_merged['factor'].ffill()
244
+
245
+ # 如果还有缺失,填充为1
246
+ df_merged['factor'] = df_merged['factor'].fillna(1.0)
247
+
248
+ # 应用复权因子
249
+ price_cols = ['open', 'high', 'low', 'close']
250
+
251
+ for col in price_cols:
252
+ if col in df_merged.columns:
253
+ if method == 'hfq':
254
+ # 后复权:价格 * 因子
255
+ df_merged[col] = df_merged[col] * df_merged['factor']
256
+ else:
257
+ # 前复权:价格 / 因子
258
+ df_merged[col] = df_merged[col] / df_merged['factor']
259
+
260
+ # 删除因子列
261
+ df_merged = df_merged.drop('factor', axis=1)
262
+
263
+ return df_merged
264
+
265
+
266
+ def to_adjust(df: pd.DataFrame, symbol: str, adjust: str = None) -> pd.DataFrame:
267
+ """
268
+ 复权接口(兼容mootdx的接口)
269
+
270
+ Args:
271
+ df: 原始股票数据
272
+ symbol: 股票代码
273
+ adjust: 复权方式,'qfq'/'01' 前复权,'hfq'/'02' 后复权
274
+
275
+ Returns:
276
+ 复权后的DataFrame
277
+ """
278
+ if adjust is None:
279
+ return df
280
+
281
+ # 兼容mootdx的参数格式
282
+ if adjust in ('01', 'qfq'):
283
+ method = 'qfq'
284
+ elif adjust in ('02', 'hfq'):
285
+ method = 'hfq'
286
+ else:
287
+ return df
288
+
289
+ return adjust_price(df, symbol, method)
@@ -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)