tdxlocal 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.
tdxlocal-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mengyiheng
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,127 @@
1
+ Metadata-Version: 2.4
2
+ Name: tdxlocal
3
+ Version: 0.1.0
4
+ Summary: 通达信本地二进制行情数据(.day / .lc1 / .lc5)读写库,零依赖
5
+ Author: mengyiheng
6
+ License: MIT
7
+ Keywords: tdx,tongdaxin,stock,kline,quant
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Intended Audience :: Financial and Insurance Industry
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Office/Business :: Financial :: Investment
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Provides-Extra: pandas
18
+ Requires-Dist: pandas; extra == "pandas"
19
+ Dynamic: license-file
20
+
21
+ <p align="center"><img src="assets/cover.png" alt="tdxdata" width="720"/></p>
22
+
23
+ # tdxdata — 通达信本地行情数据读写库
24
+
25
+ 读取和生成通达信(TDX)本地二进制 K 线文件:**`.day` 日线**、**`.lc1` 1 分钟线**、**`.lc5` 5 分钟线**。
26
+
27
+ - 🪶 **零第三方依赖**(pandas 可选)
28
+ - 📖 **读取**:一行代码解析为 Python 对象列表
29
+ - ✍️ **生成 / 增量更新**:与通达信客户端落盘格式逐字节一致,可被客户端直接识别
30
+ - 🗂 **vipdoc 目录约定**:按证券代码自动定位文件、自动推断市场(sh/sz/bj)
31
+ - 🖥 **命令行**:`tdxdata day <vipdoc> 000001 out.csv` 一键导出 CSV
32
+
33
+ ## 安装
34
+
35
+ ```bash
36
+ pip install tdxlocal # 含 pandas 支持
37
+ pip install tdxlocal --no-deps # 纯标准库
38
+ ```
39
+
40
+ ## 文件格式
41
+
42
+ 两类文件均为 **每条记录 32 字节、小端 `<IIIIIfII>`**,但日期字段编码不同:
43
+
44
+ | 字段 | 日线 `.day` | 分钟线 `.lc1/.lc5` |
45
+ |---|---|---|
46
+ | 0 | 日期 `YYYYMMDD` 明文 | 紧凑日期 `(year-2004)*2048 + month*100 + day` |
47
+ | 4 | 开*100 | 时间 `hour*60 + minute` |
48
+ | 8–20 | 高/低/收 *100 | 高/低/收/开 *100 |
49
+ | 24 | 成交额 float32(元) | 成交额 float32(元) |
50
+ | 28 | 成交量(手)+ 保留字段 | 成交量(手) |
51
+
52
+ ## 快速开始
53
+
54
+ ```python
55
+ from tdxdata import read_day, read_minute, write_day, DayBar
56
+
57
+ # 读取日线
58
+ bars = read_day(r"D:\new_tdx64\vipdoc\sh\lday\sh000001.day")
59
+ print(bars[-1]) # DayBar(date=20260116, open=..., high=..., ...)
60
+
61
+ # 读取 1 分钟线
62
+ mins = read_minute(r"D:\new_tdx\vipdoc\sh\minline\sh999999.lc1")
63
+
64
+ # 生成日线文件(通达信可直接打开)
65
+ write_day("my.day", [DayBar(20260116, 10.0, 10.5, 9.9, 10.2, 1.2e8, 123456)])
66
+
67
+ # 增量更新(追加模式,与通达信盘中落盘一致)
68
+ write_day(path, [DayBar(...)], append=True)
69
+ ```
70
+
71
+ ### vipdoc 便捷入口
72
+
73
+ ```python
74
+ from tdxdata import VipdocStore
75
+
76
+ store = VipdocStore(r"D:\new_tdx64\vipdoc")
77
+ bars = store.read("600519", "day") # 自动定位 sh/lday/sh600519.day
78
+ store.update("600519", [DayBar(...)]) # 增量追加
79
+ ```
80
+
81
+ ### 可选:输出 pandas DataFrame
82
+
83
+ ```python
84
+ import pandas as pd
85
+ from dataclasses import asdict
86
+ from tdxdata import read_day
87
+
88
+ df = pd.DataFrame([asdict(b) for b in read_day(path)])
89
+ ```
90
+
91
+ ### 命令行
92
+
93
+ ```bash
94
+ tdxdata day D:\new_tdx64\vipdoc 000001 sh000001.csv
95
+ tdxdata lc1 D:\new_tdx\vipdoc 999999 sh999999.csv
96
+ python -m tdxdata day D:\new_tdx64\vipdoc\sh\lday\sh000001.day out.csv
97
+ ```
98
+
99
+ ## 运行测试
100
+
101
+ ```bash
102
+ pip install -e . pytest
103
+ pytest
104
+ ```
105
+
106
+ 测试自带合成数据做逐字节往返校验;若本机存在 `D:\new_tdx64\vipdoc`,会自动附加真实数据冒烟测试(缺失则跳过)。
107
+
108
+ ## 免责声明
109
+
110
+ 本项目仅读取/生成**本地已有**的通达信数据文件,不提供任何行情数据,不构成任何投资建议。数据仅供学习与量化研究使用,不作为实盘交易依据,使用前请[阅读完整免责声明](DISCLAIMER.md)。**投资有风险,入市需谨慎。**
111
+
112
+ ## 量化相关定制需求
113
+
114
+ 作者承接基于本库及量化研究场景的**定制开发**,包括但不限于:
115
+
116
+ | 方向 | 示例 |
117
+ |---|---|
118
+ | 📈 数据扩展 | `.zst`/`.档位` 等其他 TDX 文件解析、盘中增量落盘监控、CSV/parquet/SQLite 批量导出 |
119
+ | 🐍 量化工具链 | pandas/Polars 适配层、多周期重采样(1m→5m→30m→日线)、复权因子处理 |
120
+ | 🤖 策略工程 | 回测框架对接、实盘信号监控脚本、通达信客户端联动(公式批量调用/行情订阅) |
121
+ | 🛠 私有部署 | 数据管线搭建、性能优化(批量解析全市场文件)、CI/自动发版 |
122
+
123
+ **联系合作**:[Gitee Issues](https://gitee.com/mengyiheng/tdxdata/issues) 提需求描述,或通过 [Gitee 私信](https://gitee.com/mengyiheng) 联系。复杂需求请附上:数据量级、时效要求(日终/盘中)、期望交付形式。
124
+
125
+ ## License
126
+
127
+ MIT
@@ -0,0 +1,107 @@
1
+ <p align="center"><img src="assets/cover.png" alt="tdxdata" width="720"/></p>
2
+
3
+ # tdxdata — 通达信本地行情数据读写库
4
+
5
+ 读取和生成通达信(TDX)本地二进制 K 线文件:**`.day` 日线**、**`.lc1` 1 分钟线**、**`.lc5` 5 分钟线**。
6
+
7
+ - 🪶 **零第三方依赖**(pandas 可选)
8
+ - 📖 **读取**:一行代码解析为 Python 对象列表
9
+ - ✍️ **生成 / 增量更新**:与通达信客户端落盘格式逐字节一致,可被客户端直接识别
10
+ - 🗂 **vipdoc 目录约定**:按证券代码自动定位文件、自动推断市场(sh/sz/bj)
11
+ - 🖥 **命令行**:`tdxdata day <vipdoc> 000001 out.csv` 一键导出 CSV
12
+
13
+ ## 安装
14
+
15
+ ```bash
16
+ pip install tdxlocal # 含 pandas 支持
17
+ pip install tdxlocal --no-deps # 纯标准库
18
+ ```
19
+
20
+ ## 文件格式
21
+
22
+ 两类文件均为 **每条记录 32 字节、小端 `<IIIIIfII>`**,但日期字段编码不同:
23
+
24
+ | 字段 | 日线 `.day` | 分钟线 `.lc1/.lc5` |
25
+ |---|---|---|
26
+ | 0 | 日期 `YYYYMMDD` 明文 | 紧凑日期 `(year-2004)*2048 + month*100 + day` |
27
+ | 4 | 开*100 | 时间 `hour*60 + minute` |
28
+ | 8–20 | 高/低/收 *100 | 高/低/收/开 *100 |
29
+ | 24 | 成交额 float32(元) | 成交额 float32(元) |
30
+ | 28 | 成交量(手)+ 保留字段 | 成交量(手) |
31
+
32
+ ## 快速开始
33
+
34
+ ```python
35
+ from tdxdata import read_day, read_minute, write_day, DayBar
36
+
37
+ # 读取日线
38
+ bars = read_day(r"D:\new_tdx64\vipdoc\sh\lday\sh000001.day")
39
+ print(bars[-1]) # DayBar(date=20260116, open=..., high=..., ...)
40
+
41
+ # 读取 1 分钟线
42
+ mins = read_minute(r"D:\new_tdx\vipdoc\sh\minline\sh999999.lc1")
43
+
44
+ # 生成日线文件(通达信可直接打开)
45
+ write_day("my.day", [DayBar(20260116, 10.0, 10.5, 9.9, 10.2, 1.2e8, 123456)])
46
+
47
+ # 增量更新(追加模式,与通达信盘中落盘一致)
48
+ write_day(path, [DayBar(...)], append=True)
49
+ ```
50
+
51
+ ### vipdoc 便捷入口
52
+
53
+ ```python
54
+ from tdxdata import VipdocStore
55
+
56
+ store = VipdocStore(r"D:\new_tdx64\vipdoc")
57
+ bars = store.read("600519", "day") # 自动定位 sh/lday/sh600519.day
58
+ store.update("600519", [DayBar(...)]) # 增量追加
59
+ ```
60
+
61
+ ### 可选:输出 pandas DataFrame
62
+
63
+ ```python
64
+ import pandas as pd
65
+ from dataclasses import asdict
66
+ from tdxdata import read_day
67
+
68
+ df = pd.DataFrame([asdict(b) for b in read_day(path)])
69
+ ```
70
+
71
+ ### 命令行
72
+
73
+ ```bash
74
+ tdxdata day D:\new_tdx64\vipdoc 000001 sh000001.csv
75
+ tdxdata lc1 D:\new_tdx\vipdoc 999999 sh999999.csv
76
+ python -m tdxdata day D:\new_tdx64\vipdoc\sh\lday\sh000001.day out.csv
77
+ ```
78
+
79
+ ## 运行测试
80
+
81
+ ```bash
82
+ pip install -e . pytest
83
+ pytest
84
+ ```
85
+
86
+ 测试自带合成数据做逐字节往返校验;若本机存在 `D:\new_tdx64\vipdoc`,会自动附加真实数据冒烟测试(缺失则跳过)。
87
+
88
+ ## 免责声明
89
+
90
+ 本项目仅读取/生成**本地已有**的通达信数据文件,不提供任何行情数据,不构成任何投资建议。数据仅供学习与量化研究使用,不作为实盘交易依据,使用前请[阅读完整免责声明](DISCLAIMER.md)。**投资有风险,入市需谨慎。**
91
+
92
+ ## 量化相关定制需求
93
+
94
+ 作者承接基于本库及量化研究场景的**定制开发**,包括但不限于:
95
+
96
+ | 方向 | 示例 |
97
+ |---|---|
98
+ | 📈 数据扩展 | `.zst`/`.档位` 等其他 TDX 文件解析、盘中增量落盘监控、CSV/parquet/SQLite 批量导出 |
99
+ | 🐍 量化工具链 | pandas/Polars 适配层、多周期重采样(1m→5m→30m→日线)、复权因子处理 |
100
+ | 🤖 策略工程 | 回测框架对接、实盘信号监控脚本、通达信客户端联动(公式批量调用/行情订阅) |
101
+ | 🛠 私有部署 | 数据管线搭建、性能优化(批量解析全市场文件)、CI/自动发版 |
102
+
103
+ **联系合作**:[Gitee Issues](https://gitee.com/mengyiheng/tdxdata/issues) 提需求描述,或通过 [Gitee 私信](https://gitee.com/mengyiheng) 联系。复杂需求请附上:数据量级、时效要求(日终/盘中)、期望交付形式。
104
+
105
+ ## License
106
+
107
+ MIT
@@ -0,0 +1,30 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tdxlocal"
7
+ version = "0.1.0"
8
+ description = "通达信本地二进制行情数据(.day / .lc1 / .lc5)读写库,零依赖"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "mengyiheng" }]
13
+ keywords = ["tdx", "tongdaxin", "stock", "kline", "quant"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "Intended Audience :: Financial and Insurance Industry",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Topic :: Office/Business :: Financial :: Investment",
21
+ ]
22
+
23
+ [project.optional-dependencies]
24
+ pandas = ["pandas"]
25
+
26
+ [project.scripts]
27
+ tdxdata = "tdxdata.cli:main"
28
+
29
+ [tool.setuptools.packages.find]
30
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,27 @@
1
+ """tdxdata — 读写通达信本地二进制行情数据(.day / .lc1 / .lc5)。
2
+
3
+ 零第三方依赖;提供 pandas 支持时可选输出 DataFrame。
4
+
5
+ 免责声明:本库仅处理本地已有数据文件,不提供任何行情数据,不构成投资建议。
6
+ 数据仅供学习与量化研究使用,详见项目根目录 DISCLAIMER.md。
7
+
8
+ 快速开始::
9
+
10
+ from tdxdata import read_day, read_minute
11
+
12
+ bars = read_day(r"D:\\new_tdx64\\vipdoc\\sh\\lday\\sh000001.day")
13
+ mins = read_minute(r"D:\\new_tdx\\vipdoc\\sh\\minline\\sh999999.lc1")
14
+ """
15
+
16
+ from .day import DayBar, read_day, write_day
17
+ from .minute import MinuteBar, read_minute, write_minute
18
+ from .vipdoc import VipdocStore, find_bar_file
19
+
20
+ __version__ = "0.1.0"
21
+
22
+ __all__ = [
23
+ "DayBar", "read_day", "write_day",
24
+ "MinuteBar", "read_minute", "write_minute",
25
+ "VipdocStore", "find_bar_file",
26
+ "__version__",
27
+ ]
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1,44 @@
1
+ """命令行工具:python -m tdxdata <day|lc1|lc5> <文件或数据目录> <代码> [输出.csv]"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import csv
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ from .day import read_day
10
+ from .minute import read_minute
11
+ from .vipdoc import find_bar_file
12
+
13
+
14
+ def _dump(rows, header, out_path: str) -> None:
15
+ with open(out_path, "w", newline="", encoding="utf-8-sig") as f:
16
+ w = csv.writer(f)
17
+ w.writerow(header)
18
+ w.writerows([getattr(r, k) for k in header] for r in rows)
19
+ print(f"[tdxdata] {len(rows)} 条 -> {out_path}")
20
+
21
+
22
+ def main(argv: list[str] | None = None) -> int:
23
+ argv = list(sys.argv[1:] if argv is None else argv)
24
+ if len(argv) < 2:
25
+ print(__doc__)
26
+ return 1
27
+ kind, target = argv[0].lower(), argv[1]
28
+ code = argv[2] if len(argv) > 2 else None
29
+ out = argv[3] if len(argv) > 3 else "tdx_export.csv"
30
+
31
+ if kind == "day":
32
+ path = Path(target) if Path(target).is_file() else find_bar_file(target, code or "", "day")
33
+ _dump(read_day(path), ["date", "open", "high", "low", "close", "amount", "vol"], out)
34
+ elif kind in ("lc1", "lc5"):
35
+ path = Path(target) if Path(target).is_file() else find_bar_file(target, code or "", kind)
36
+ _dump(read_minute(path), ["datetime", "open", "high", "low", "close", "amount", "vol"], out)
37
+ else:
38
+ print(f"未知类型: {kind}")
39
+ return 1
40
+ return 0
41
+
42
+
43
+ if __name__ == "__main__":
44
+ raise SystemExit(main())
@@ -0,0 +1,60 @@
1
+ """通达信日线文件(vipdoc/<market>/lday/*.day)读写。
2
+
3
+ 文件格式:每条记录 32 字节,小端 ``<IIIIIfII>``:
4
+
5
+ ====== ======== ============================
6
+ 偏移 类型 含义
7
+ ====== ======== ============================
8
+ 0 uint32 日期 YYYYMMDD
9
+ 4 uint32 开盘价 * 100
10
+ 8 uint32 最高价 * 100
11
+ 12 uint32 最低价 * 100
12
+ 16 uint32 收盘价 * 100
13
+ 20 float32 成交额(元)
14
+ 24 uint32 成交量(手)
15
+ 28 uint32 保留字段(通常为 0)
16
+ ====== ======== ============================
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import struct
22
+ from pathlib import Path
23
+ from typing import Iterable, List
24
+
25
+ from .records import DayBar
26
+
27
+ _STRUCT = struct.Struct("<IIIIIfII")
28
+ _PRICE_SCALE = 100.0
29
+
30
+
31
+ def read_day(path: str | Path) -> List[DayBar]:
32
+ """读取 .day 文件,按文件顺序返回 K 线列表。"""
33
+ data = Path(path).read_bytes()
34
+ n = len(data) // _STRUCT.size
35
+ bars: List[DayBar] = []
36
+ for i in range(n):
37
+ date, o, h, l, c, amount, vol, _ = _STRUCT.unpack_from(data, i * _STRUCT.size)
38
+ bars.append(DayBar(date, o / _PRICE_SCALE, h / _PRICE_SCALE,
39
+ l / _PRICE_SCALE, c / _PRICE_SCALE,
40
+ round(amount, 2), float(vol)))
41
+ return bars
42
+
43
+
44
+ def write_day(path: str | Path, bars: Iterable[DayBar], *, append: bool = False) -> int:
45
+ """把 K 线序列写入 .day 文件,返回写入条数。
46
+
47
+ append=True 时追加到文件末尾(用于增量更新,与通达信自身行为一致)。
48
+ """
49
+ payload = bytearray()
50
+ count = 0
51
+ for b in bars:
52
+ payload += _STRUCT.pack(
53
+ int(b.date), round(b.open * _PRICE_SCALE), round(b.high * _PRICE_SCALE),
54
+ round(b.low * _PRICE_SCALE), round(b.close * _PRICE_SCALE),
55
+ float(b.amount), int(b.vol), 0)
56
+ count += 1
57
+ mode = "ab" if append else "wb"
58
+ with open(path, mode) as f:
59
+ f.write(payload)
60
+ return count
@@ -0,0 +1,83 @@
1
+ """通达信分钟线文件(vipdoc/<market>/minline/*.lc1 | *.lc5)读写。
2
+
3
+ 文件格式:每条记录 32 字节,小端 ``<IIIIIfII>``,但日期字段是**紧凑编码**:
4
+
5
+ ====== ======== ====================================================
6
+ 偏移 类型 含义
7
+ ====== ======== ====================================================
8
+ 0 uint32 紧凑日期:``year = d // 2048 + 2004``;
9
+ ``month = (d % 2048) // 100``;``day = (d % 2048) % 100``
10
+ 4 uint32 时间:``hour = t // 60``;``minute = t % 60``
11
+ 8 uint32 开盘价 * 100
12
+ 12 uint32 最高价 * 100
13
+ 16 uint32 最低价 * 100
14
+ 20 uint32 收盘价 * 100
15
+ 24 float32 成交额(元)
16
+ 28 uint32 成交量(手)
17
+ ====== ======== ====================================================
18
+
19
+ 注意与日线不同:分钟线没有保留字段,最后一个 uint32 就是成交量。
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import struct
25
+ from pathlib import Path
26
+ from typing import Iterable, List
27
+
28
+ from .records import MinuteBar
29
+
30
+ _STRUCT = struct.Struct("<IIIIIIfI")
31
+ _PRICE_SCALE = 100.0
32
+ _EPOCH_YEAR = 2004
33
+
34
+
35
+ def _unpack_date(raw: int) -> tuple[int, int, int]:
36
+ rest = raw % 2048
37
+ return raw // 2048 + _EPOCH_YEAR, rest // 100, rest % 100
38
+
39
+
40
+ def _pack_date(year: int, month: int, day: int) -> int:
41
+ return (year - _EPOCH_YEAR) * 2048 + month * 100 + day
42
+
43
+
44
+ def read_minute(path: str | Path) -> List[MinuteBar]:
45
+ """读取 .lc1 / .lc5 文件,按文件顺序返回分钟 K 线列表。"""
46
+ data = Path(path).read_bytes()
47
+ n = len(data) // _STRUCT.size
48
+ bars: List[MinuteBar] = []
49
+ for i in range(n):
50
+ raw_date, raw_time, o, h, l, c, amount, vol = _STRUCT.unpack_from(data, i * _STRUCT.size)
51
+ year, month, day = _unpack_date(raw_date)
52
+ hour, minute = divmod(raw_time, 60)
53
+ bars.append(MinuteBar(
54
+ f"{year:04d}-{month:02d}-{day:02d} {hour:02d}:{minute:02d}",
55
+ o / _PRICE_SCALE, h / _PRICE_SCALE, l / _PRICE_SCALE, c / _PRICE_SCALE,
56
+ round(amount, 2), float(vol)))
57
+ return bars
58
+
59
+
60
+ def write_minute(path: str | Path, bars: Iterable[MinuteBar], *, append: bool = False) -> int:
61
+ """把分钟 K 线序列写入 .lc1 / .lc5 文件,返回写入条数。
62
+
63
+ ``datetime`` 接受 ``"YYYY-MM-DD HH:MM"`` 或 ``"YYYYMMDD HHMM"`` 两种形式。
64
+ """
65
+ payload = bytearray()
66
+ count = 0
67
+ for b in bars:
68
+ parts = "".join(ch if ch.isdigit() else " " for ch in b.datetime).split()
69
+ if len(parts) >= 5: # "2026-01-15 09:31"
70
+ year, month, day, hour, minute = (int(x) for x in parts[:5])
71
+ else: # "20260116 1405"
72
+ year, month, day = int(parts[0][:4]), int(parts[0][4:6]), int(parts[0][6:8])
73
+ hour, minute = int(parts[1][:2]), int(parts[1][2:4])
74
+ payload += _STRUCT.pack(
75
+ _pack_date(year, month, day), hour * 60 + minute,
76
+ round(b.open * _PRICE_SCALE), round(b.high * _PRICE_SCALE),
77
+ round(b.low * _PRICE_SCALE), round(b.close * _PRICE_SCALE),
78
+ float(b.amount), int(b.vol))
79
+ count += 1
80
+ mode = "ab" if append else "wb"
81
+ with open(path, mode) as f:
82
+ f.write(payload)
83
+ return count
@@ -0,0 +1,37 @@
1
+ """基础 K 线记录结构。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+
8
+ @dataclass(frozen=True, slots=True)
9
+ class DayBar:
10
+ """日线一根 K 线。
11
+
12
+ amount 单位为元,vol 单位为手(与通达信原始文件一致)。
13
+ """
14
+
15
+ date: int # YYYYMMDD
16
+ open: float
17
+ high: float
18
+ low: float
19
+ close: float
20
+ amount: float
21
+ vol: float
22
+
23
+
24
+ @dataclass(frozen=True, slots=True)
25
+ class MinuteBar:
26
+ """分钟线一根 K 线(lc1 = 1 分钟,lc5 = 5 分钟)。
27
+
28
+ amount 单位为元,vol 单位为手。
29
+ """
30
+
31
+ datetime: str # "YYYY-MM-DD HH:MM"
32
+ open: float
33
+ high: float
34
+ low: float
35
+ close: float
36
+ amount: float
37
+ vol: float
@@ -0,0 +1,57 @@
1
+ """按通达信 vipdoc 目录约定定位数据文件。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ _MARKET_BY_PREFIX = {"sh": 1, "sz": 0, "bj": 2}
8
+
9
+
10
+ def market_of(code: str) -> str:
11
+ """根据 6 位代码推断市场(sh/sz/bj)。"""
12
+ if code.startswith(("6", "5", "9")):
13
+ return "sh"
14
+ if code.startswith(("4", "8")):
15
+ return "bj"
16
+ return "sz"
17
+
18
+
19
+ def find_bar_file(vipdoc: str | Path, code: str, kind: str) -> Path:
20
+ """在 vipdoc 目录中定位单个证券的数据文件。
21
+
22
+ kind: ``"day"`` -> lday/*.day;``"lc1"``/``"lc5"`` -> minline/*.lc1|*.lc5
23
+ """
24
+ kind = kind.lower()
25
+ market = market_of(code)
26
+ if kind == "day":
27
+ p = Path(vipdoc) / market / "lday" / f"{market}{code}.day"
28
+ elif kind in ("lc1", "lc5", "1", "5", "min1", "min5"):
29
+ suffix = "lc1" if kind in ("lc1", "1", "min1") else "lc5"
30
+ p = Path(vipdoc) / market / "minline" / f"{market}{code}.{suffix}"
31
+ else:
32
+ raise ValueError(f"未知类型: {kind!r}(支持 day / lc1 / lc5)")
33
+ if not p.exists():
34
+ raise FileNotFoundError(f"未找到数据文件: {p}")
35
+ return p
36
+
37
+
38
+ class VipdocStore:
39
+ """vipdoc 目录的便捷入口,自动识别市场前缀并读写。"""
40
+
41
+ def __init__(self, vipdoc: str | Path):
42
+ self.vipdoc = Path(vipdoc)
43
+
44
+ def read(self, code: str, kind: str = "day"):
45
+ """按代码读取 K 线。kind: day / lc1 / lc5。"""
46
+ path = find_bar_file(self.vipdoc, code, kind)
47
+ from . import day as _day, minute as _minute
48
+
49
+ return _day.read_day(path) if kind.lower() == "day" else _minute.read_minute(path)
50
+
51
+ def update(self, code: str, bars, kind: str = "day") -> int:
52
+ """增量追加 K 线(与通达信盘中落盘方式一致)。"""
53
+ path = find_bar_file(self.vipdoc, code, kind)
54
+ from . import day as _day, minute as _minute
55
+
56
+ writer = _day.write_day if kind.lower() == "day" else _minute.write_minute
57
+ return writer(path, bars, append=True)
@@ -0,0 +1,127 @@
1
+ Metadata-Version: 2.4
2
+ Name: tdxlocal
3
+ Version: 0.1.0
4
+ Summary: 通达信本地二进制行情数据(.day / .lc1 / .lc5)读写库,零依赖
5
+ Author: mengyiheng
6
+ License: MIT
7
+ Keywords: tdx,tongdaxin,stock,kline,quant
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Intended Audience :: Financial and Insurance Industry
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Office/Business :: Financial :: Investment
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Provides-Extra: pandas
18
+ Requires-Dist: pandas; extra == "pandas"
19
+ Dynamic: license-file
20
+
21
+ <p align="center"><img src="assets/cover.png" alt="tdxdata" width="720"/></p>
22
+
23
+ # tdxdata — 通达信本地行情数据读写库
24
+
25
+ 读取和生成通达信(TDX)本地二进制 K 线文件:**`.day` 日线**、**`.lc1` 1 分钟线**、**`.lc5` 5 分钟线**。
26
+
27
+ - 🪶 **零第三方依赖**(pandas 可选)
28
+ - 📖 **读取**:一行代码解析为 Python 对象列表
29
+ - ✍️ **生成 / 增量更新**:与通达信客户端落盘格式逐字节一致,可被客户端直接识别
30
+ - 🗂 **vipdoc 目录约定**:按证券代码自动定位文件、自动推断市场(sh/sz/bj)
31
+ - 🖥 **命令行**:`tdxdata day <vipdoc> 000001 out.csv` 一键导出 CSV
32
+
33
+ ## 安装
34
+
35
+ ```bash
36
+ pip install tdxlocal # 含 pandas 支持
37
+ pip install tdxlocal --no-deps # 纯标准库
38
+ ```
39
+
40
+ ## 文件格式
41
+
42
+ 两类文件均为 **每条记录 32 字节、小端 `<IIIIIfII>`**,但日期字段编码不同:
43
+
44
+ | 字段 | 日线 `.day` | 分钟线 `.lc1/.lc5` |
45
+ |---|---|---|
46
+ | 0 | 日期 `YYYYMMDD` 明文 | 紧凑日期 `(year-2004)*2048 + month*100 + day` |
47
+ | 4 | 开*100 | 时间 `hour*60 + minute` |
48
+ | 8–20 | 高/低/收 *100 | 高/低/收/开 *100 |
49
+ | 24 | 成交额 float32(元) | 成交额 float32(元) |
50
+ | 28 | 成交量(手)+ 保留字段 | 成交量(手) |
51
+
52
+ ## 快速开始
53
+
54
+ ```python
55
+ from tdxdata import read_day, read_minute, write_day, DayBar
56
+
57
+ # 读取日线
58
+ bars = read_day(r"D:\new_tdx64\vipdoc\sh\lday\sh000001.day")
59
+ print(bars[-1]) # DayBar(date=20260116, open=..., high=..., ...)
60
+
61
+ # 读取 1 分钟线
62
+ mins = read_minute(r"D:\new_tdx\vipdoc\sh\minline\sh999999.lc1")
63
+
64
+ # 生成日线文件(通达信可直接打开)
65
+ write_day("my.day", [DayBar(20260116, 10.0, 10.5, 9.9, 10.2, 1.2e8, 123456)])
66
+
67
+ # 增量更新(追加模式,与通达信盘中落盘一致)
68
+ write_day(path, [DayBar(...)], append=True)
69
+ ```
70
+
71
+ ### vipdoc 便捷入口
72
+
73
+ ```python
74
+ from tdxdata import VipdocStore
75
+
76
+ store = VipdocStore(r"D:\new_tdx64\vipdoc")
77
+ bars = store.read("600519", "day") # 自动定位 sh/lday/sh600519.day
78
+ store.update("600519", [DayBar(...)]) # 增量追加
79
+ ```
80
+
81
+ ### 可选:输出 pandas DataFrame
82
+
83
+ ```python
84
+ import pandas as pd
85
+ from dataclasses import asdict
86
+ from tdxdata import read_day
87
+
88
+ df = pd.DataFrame([asdict(b) for b in read_day(path)])
89
+ ```
90
+
91
+ ### 命令行
92
+
93
+ ```bash
94
+ tdxdata day D:\new_tdx64\vipdoc 000001 sh000001.csv
95
+ tdxdata lc1 D:\new_tdx\vipdoc 999999 sh999999.csv
96
+ python -m tdxdata day D:\new_tdx64\vipdoc\sh\lday\sh000001.day out.csv
97
+ ```
98
+
99
+ ## 运行测试
100
+
101
+ ```bash
102
+ pip install -e . pytest
103
+ pytest
104
+ ```
105
+
106
+ 测试自带合成数据做逐字节往返校验;若本机存在 `D:\new_tdx64\vipdoc`,会自动附加真实数据冒烟测试(缺失则跳过)。
107
+
108
+ ## 免责声明
109
+
110
+ 本项目仅读取/生成**本地已有**的通达信数据文件,不提供任何行情数据,不构成任何投资建议。数据仅供学习与量化研究使用,不作为实盘交易依据,使用前请[阅读完整免责声明](DISCLAIMER.md)。**投资有风险,入市需谨慎。**
111
+
112
+ ## 量化相关定制需求
113
+
114
+ 作者承接基于本库及量化研究场景的**定制开发**,包括但不限于:
115
+
116
+ | 方向 | 示例 |
117
+ |---|---|
118
+ | 📈 数据扩展 | `.zst`/`.档位` 等其他 TDX 文件解析、盘中增量落盘监控、CSV/parquet/SQLite 批量导出 |
119
+ | 🐍 量化工具链 | pandas/Polars 适配层、多周期重采样(1m→5m→30m→日线)、复权因子处理 |
120
+ | 🤖 策略工程 | 回测框架对接、实盘信号监控脚本、通达信客户端联动(公式批量调用/行情订阅) |
121
+ | 🛠 私有部署 | 数据管线搭建、性能优化(批量解析全市场文件)、CI/自动发版 |
122
+
123
+ **联系合作**:[Gitee Issues](https://gitee.com/mengyiheng/tdxdata/issues) 提需求描述,或通过 [Gitee 私信](https://gitee.com/mengyiheng) 联系。复杂需求请附上:数据量级、时效要求(日终/盘中)、期望交付形式。
124
+
125
+ ## License
126
+
127
+ MIT
@@ -0,0 +1,17 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/tdxdata/__init__.py
5
+ src/tdxdata/__main__.py
6
+ src/tdxdata/cli.py
7
+ src/tdxdata/day.py
8
+ src/tdxdata/minute.py
9
+ src/tdxdata/records.py
10
+ src/tdxdata/vipdoc.py
11
+ src/tdxlocal.egg-info/PKG-INFO
12
+ src/tdxlocal.egg-info/SOURCES.txt
13
+ src/tdxlocal.egg-info/dependency_links.txt
14
+ src/tdxlocal.egg-info/entry_points.txt
15
+ src/tdxlocal.egg-info/requires.txt
16
+ src/tdxlocal.egg-info/top_level.txt
17
+ tests/test_tdxdata.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ tdxdata = tdxdata.cli:main
@@ -0,0 +1,3 @@
1
+
2
+ [pandas]
3
+ pandas
@@ -0,0 +1 @@
1
+ tdxdata
@@ -0,0 +1,97 @@
1
+ import io
2
+ import struct
3
+ from dataclasses import asdict
4
+ from pathlib import Path
5
+
6
+ import pytest
7
+
8
+ from tdxdata import DayBar, MinuteBar, read_day, read_minute, write_day, write_minute
9
+ from tdxdata.vipdoc import market_of
10
+
11
+ # ---------- 合成数据:逐字节往返 ----------
12
+
13
+ def test_day_roundtrip(tmp_path):
14
+ bars = [
15
+ DayBar(20260115, 10.0, 10.5, 9.9, 10.2, 1.234e8, 123456),
16
+ DayBar(20260116, 10.2, 10.8, 10.1, 10.6, 9.876e7, 98765),
17
+ ]
18
+ p = tmp_path / "test.day"
19
+ assert write_day(p, bars) == 2
20
+ assert p.stat().st_size == 64
21
+
22
+ # 手工核对原始字节:第一条
23
+ raw = struct.unpack("<IIIIIfII", p.read_bytes()[:32])
24
+ assert raw[0] == 20260115
25
+ assert raw[1] == 1000 and raw[2] == 1050 and raw[3] == 990 and raw[4] == 1020
26
+ assert raw[6] == 123456
27
+
28
+ back = read_day(p)
29
+ assert back[0].date == 20260115
30
+ assert back[0].open == pytest.approx(10.0)
31
+ assert back[1].close == pytest.approx(10.6)
32
+ assert [asdict(b) for b in back][1]["vol"] == 98765
33
+
34
+
35
+ def test_day_append(tmp_path):
36
+ p = tmp_path / "a.day"
37
+ write_day(p, [DayBar(20260101, 1, 1, 1, 1, 0, 1)])
38
+ write_day(p, [DayBar(20260102, 1, 1, 1, 1, 0, 2)], append=True)
39
+ assert len(read_day(p)) == 2
40
+
41
+
42
+ def test_minute_roundtrip(tmp_path):
43
+ bars = [
44
+ MinuteBar("2026-01-15 09:31", 3.45, 3.5, 3.4, 3.48, 5e6, 1500),
45
+ MinuteBar("2026-01-15 09:32", 3.48, 3.52, 3.47, 3.5, 6e6, 1800),
46
+ ]
47
+ p = tmp_path / "test.lc5"
48
+ assert write_minute(p, bars) == 2
49
+ assert p.stat().st_size == 64
50
+
51
+ # 紧凑日期字段核对: (2026-2004)*2048 + 1*100 + 15
52
+ raw = struct.unpack("<IIIIIfII", p.read_bytes()[:32])
53
+ assert raw[0] == (2026 - 2004) * 2048 + 115
54
+ assert raw[1] == 9 * 60 + 31
55
+
56
+ back = read_minute(p)
57
+ assert back[0].datetime == "2026-01-15 09:31"
58
+ assert back[0].open == pytest.approx(3.45)
59
+ assert back[1].high == pytest.approx(3.52)
60
+
61
+
62
+ def test_minute_datetime_alt_format(tmp_path):
63
+ p = tmp_path / "b.lc1"
64
+ write_minute(p, [MinuteBar("20260116 1405", 1, 1, 1, 1, 0, 1)])
65
+ assert read_minute(p)[0].datetime == "2026-01-16 14:05"
66
+
67
+
68
+ def test_market_of():
69
+ assert market_of("600519") == "sh"
70
+ assert market_of("510300") == "sh"
71
+ assert market_of("000001") == "sz"
72
+ assert market_of("300750") == "sz"
73
+ assert market_of("830799") == "bj"
74
+
75
+
76
+ # ---------- 真实数据冒烟(本机有通达信数据时) ----------
77
+
78
+ REAL = Path(r"D:\new_tdx64\vipdoc\sh\lday\sh000001.day")
79
+
80
+
81
+ @pytest.mark.skipif(not REAL.exists(), reason="本机无通达信日线数据")
82
+ def test_real_day_file():
83
+ bars = read_day(REAL)
84
+ assert len(bars) == REAL.stat().st_size // 32
85
+ assert bars == sorted(bars, key=lambda b: b.date)
86
+ last = bars[-1]
87
+ assert 1990 <= last.date // 10000 <= 2100
88
+ assert last.low <= last.open <= last.high
89
+ assert last.low <= last.close <= last.high
90
+ # 写出再读回,逐条一致(金额 float32 有 ~7 位精度)
91
+ out = io.BytesIO()
92
+ p = Path("_)tmp_check.day")
93
+ try:
94
+ write_day(p, bars)
95
+ assert read_day(p) == bars
96
+ finally:
97
+ p.unlink(missing_ok=True)