tdxdraw 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.
- tdxdraw-0.1.0/LICENSE +21 -0
- tdxdraw-0.1.0/PKG-INFO +139 -0
- tdxdraw-0.1.0/README.md +121 -0
- tdxdraw-0.1.0/pyproject.toml +27 -0
- tdxdraw-0.1.0/setup.cfg +4 -0
- tdxdraw-0.1.0/src/tdxdraw/__init__.py +25 -0
- tdxdraw-0.1.0/src/tdxdraw/__main__.py +3 -0
- tdxdraw-0.1.0/src/tdxdraw/batch.py +65 -0
- tdxdraw-0.1.0/src/tdxdraw/cli.py +38 -0
- tdxdraw-0.1.0/src/tdxdraw/eld.py +176 -0
- tdxdraw-0.1.0/src/tdxdraw.egg-info/PKG-INFO +139 -0
- tdxdraw-0.1.0/src/tdxdraw.egg-info/SOURCES.txt +14 -0
- tdxdraw-0.1.0/src/tdxdraw.egg-info/dependency_links.txt +1 -0
- tdxdraw-0.1.0/src/tdxdraw.egg-info/entry_points.txt +2 -0
- tdxdraw-0.1.0/src/tdxdraw.egg-info/top_level.txt +1 -0
- tdxdraw-0.1.0/tests/test_eld.py +69 -0
tdxdraw-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.
|
tdxdraw-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tdxdraw
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 通达信画线文件(.eld)读写与批量生成工具,零依赖
|
|
5
|
+
Author: mengyiheng
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: tdx,tongdaxin,drawline,eld,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
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
<p align="center"><img src="assets/cover.png" alt="tdxdraw" width="720"/></p>
|
|
20
|
+
|
|
21
|
+
# tdxdraw — 通达信画线批量生成与转换工具
|
|
22
|
+
|
|
23
|
+
把**任意来源的画线坐标清单**(CSV / JSON / 你的分析程序输出)批量转成通达信官方
|
|
24
|
+
「画线导入」功能可直接识别的 `.eld` 文件;也可把通达信导出的画线文件反向解析成
|
|
25
|
+
CSV / JSON,做二次处理。
|
|
26
|
+
|
|
27
|
+
- 📤 **生成**:程序化批量画线 → `.eld` → 通达信一键导入
|
|
28
|
+
- 📥 **解析**:通达信导出的 `.eld` → CSV / JSON,供回测、复盘、二次分析
|
|
29
|
+
- 🪶 **零第三方依赖**,Python 3.9+
|
|
30
|
+
- ✅ **逐字节校验**:编码结果与通达信实测可导入的样本文件逐字节一致
|
|
31
|
+
- 🧭 **格式完全开源**:378 字节记录结构文档化于 [docs/FORMAT.md](docs/FORMAT.md)
|
|
32
|
+
|
|
33
|
+
## 安装
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install tdxdraw
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 快速开始
|
|
40
|
+
|
|
41
|
+
### 1. 写一个画线清单(CSV)
|
|
42
|
+
|
|
43
|
+
```csv
|
|
44
|
+
market,code,line_type,start_price,end_price,start_date,end_date,period,line_id
|
|
45
|
+
sh,600519,hline,1250.00,1250.00,2026-01-05,2026-10-19,day,1
|
|
46
|
+
sh,600519,segment,1181.64,1367.85,2026-06-12,2026-09-30,day,2
|
|
47
|
+
sz,002805,rectangle,1391.60,1321.62,2026-08-27,2026-09-15,day,1
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 2. 转成 .eld 并导入通达信
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
tdxdraw build drawings.csv mylines.eld
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
通达信 K 线界面 → 画线工具 → **画线管理(或导入画线)** → 选择 `mylines.eld`,
|
|
57
|
+
所有画线立即出现在对应个股的 K 线图上。
|
|
58
|
+
|
|
59
|
+
### 3. 也可以只用 Python API
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from datetime import date
|
|
63
|
+
from tdxdraw import Drawing, write_eld, read_eld
|
|
64
|
+
|
|
65
|
+
d = Drawing("sh", "600519", "hline", 1250.0, 1250.0,
|
|
66
|
+
date(2026, 1, 5), date(2026, 10, 19), line_id=1)
|
|
67
|
+
write_eld("mylines.eld", [d])
|
|
68
|
+
|
|
69
|
+
bars = read_eld("通达信导出.eld") # 反向解析
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 4. 反向导出为 CSV
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
tdxdraw export 通达信导出.eld lines.csv
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## 支持的画线类型
|
|
79
|
+
|
|
80
|
+
`segment`(线段) `hline`(水平直线) `ray`(直线) `arrow`(箭头) `rectangle`(矩形)
|
|
81
|
+
`text`(文字注释) `circle`(圆) `parallel`(平行直线) `channel`(价格通道线)
|
|
82
|
+
`gann_fan`(江恩角度线) `gann_percent`(百分比线) `wave`(波段线) 等 40 种,
|
|
83
|
+
完整映射见 [docs/FORMAT.md](docs/FORMAT.md)。
|
|
84
|
+
|
|
85
|
+
支持的周期:`1m 5m 15m 30m 60m day week month quarter year`。
|
|
86
|
+
|
|
87
|
+
## .eld 文件格式
|
|
88
|
+
|
|
89
|
+
每条画线 = 378 字节定长记录(小端),核心字段:
|
|
90
|
+
|
|
91
|
+
| 偏移 | 含义 |
|
|
92
|
+
|---|---|
|
|
93
|
+
| 0x00 | 市场(0=深 1=沪 2=京) |
|
|
94
|
+
| 0x01 | 6 位证券代码 |
|
|
95
|
+
| 0x18 | 指标页(如 `\x02STANDK`) |
|
|
96
|
+
| 0x56 / 0x5A | 起点 / 终点价格(float32) |
|
|
97
|
+
| 0x63 | 周期编码 |
|
|
98
|
+
| 0x65 | 画线类型 |
|
|
99
|
+
| 0x69 / 0x75 | 起点 / 终点日期(年 zone + 月日 9 位紧凑编码) |
|
|
100
|
+
| 0x6B / 0x77 | 起点 / 终点分钟数 |
|
|
101
|
+
| 0x71 | 个股内画线序号 |
|
|
102
|
+
| 0xD1 / 0xD5 / 0xD9 | 线宽 / 线型 / BGRA 颜色 |
|
|
103
|
+
|
|
104
|
+
完整字段表见 [docs/FORMAT.md](docs/FORMAT.md)。
|
|
105
|
+
|
|
106
|
+
## 运行测试
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
pip install -e . pytest
|
|
110
|
+
pytest
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
测试基准包含一个**经通达信实测可导入**的真实样本文件(逐字节比对)。
|
|
114
|
+
|
|
115
|
+
## 免责声明
|
|
116
|
+
|
|
117
|
+
本项目仅做画线坐标数据与通达信画线文件格式之间的转换,不提供任何行情数据,
|
|
118
|
+
不构成任何投资建议。使用前请[阅读完整免责声明](DISCLAIMER.md)。
|
|
119
|
+
|
|
120
|
+
## 量化相关定制需求
|
|
121
|
+
|
|
122
|
+
承接画线自动化、分析系统 → 通达信画线管道、数据格式转换等定制开发,
|
|
123
|
+
联系方式见 [README 联系方式](#量化相关定制需求) 或 [Gitee Issues](https://gitee.com/mengyiheng/tdxdraw/issues)。
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT
|
|
128
|
+
|
|
129
|
+
## 量化相关定制需求
|
|
130
|
+
|
|
131
|
+
作者承接画线自动化与通达信二次开发的定制开发:
|
|
132
|
+
|
|
133
|
+
| 方向 | 示例 |
|
|
134
|
+
|---|---|
|
|
135
|
+
| 🖍 画线工程化 | 分析系统信号 → 通达信画线管道、多周期批量落图、画线模板管理 |
|
|
136
|
+
| 📐 格式深水区 | 更多画线类型/样式区字段逆向、tdxline.dat 在线注入 |
|
|
137
|
+
| 🛠 私有部署 | 全市场批量导入、CI 自动生成画线、团队共享线组 |
|
|
138
|
+
|
|
139
|
+
**联系合作**:[Gitee Issues](https://gitee.com/mengyiheng/tdxdraw/issues) 或 [Gitee 私信](https://gitee.com/mengyiheng)。复杂需求请附:画线数据量级、时效要求、期望交付形式。
|
tdxdraw-0.1.0/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
<p align="center"><img src="assets/cover.png" alt="tdxdraw" width="720"/></p>
|
|
2
|
+
|
|
3
|
+
# tdxdraw — 通达信画线批量生成与转换工具
|
|
4
|
+
|
|
5
|
+
把**任意来源的画线坐标清单**(CSV / JSON / 你的分析程序输出)批量转成通达信官方
|
|
6
|
+
「画线导入」功能可直接识别的 `.eld` 文件;也可把通达信导出的画线文件反向解析成
|
|
7
|
+
CSV / JSON,做二次处理。
|
|
8
|
+
|
|
9
|
+
- 📤 **生成**:程序化批量画线 → `.eld` → 通达信一键导入
|
|
10
|
+
- 📥 **解析**:通达信导出的 `.eld` → CSV / JSON,供回测、复盘、二次分析
|
|
11
|
+
- 🪶 **零第三方依赖**,Python 3.9+
|
|
12
|
+
- ✅ **逐字节校验**:编码结果与通达信实测可导入的样本文件逐字节一致
|
|
13
|
+
- 🧭 **格式完全开源**:378 字节记录结构文档化于 [docs/FORMAT.md](docs/FORMAT.md)
|
|
14
|
+
|
|
15
|
+
## 安装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install tdxdraw
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 快速开始
|
|
22
|
+
|
|
23
|
+
### 1. 写一个画线清单(CSV)
|
|
24
|
+
|
|
25
|
+
```csv
|
|
26
|
+
market,code,line_type,start_price,end_price,start_date,end_date,period,line_id
|
|
27
|
+
sh,600519,hline,1250.00,1250.00,2026-01-05,2026-10-19,day,1
|
|
28
|
+
sh,600519,segment,1181.64,1367.85,2026-06-12,2026-09-30,day,2
|
|
29
|
+
sz,002805,rectangle,1391.60,1321.62,2026-08-27,2026-09-15,day,1
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 2. 转成 .eld 并导入通达信
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
tdxdraw build drawings.csv mylines.eld
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
通达信 K 线界面 → 画线工具 → **画线管理(或导入画线)** → 选择 `mylines.eld`,
|
|
39
|
+
所有画线立即出现在对应个股的 K 线图上。
|
|
40
|
+
|
|
41
|
+
### 3. 也可以只用 Python API
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from datetime import date
|
|
45
|
+
from tdxdraw import Drawing, write_eld, read_eld
|
|
46
|
+
|
|
47
|
+
d = Drawing("sh", "600519", "hline", 1250.0, 1250.0,
|
|
48
|
+
date(2026, 1, 5), date(2026, 10, 19), line_id=1)
|
|
49
|
+
write_eld("mylines.eld", [d])
|
|
50
|
+
|
|
51
|
+
bars = read_eld("通达信导出.eld") # 反向解析
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 4. 反向导出为 CSV
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
tdxdraw export 通达信导出.eld lines.csv
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## 支持的画线类型
|
|
61
|
+
|
|
62
|
+
`segment`(线段) `hline`(水平直线) `ray`(直线) `arrow`(箭头) `rectangle`(矩形)
|
|
63
|
+
`text`(文字注释) `circle`(圆) `parallel`(平行直线) `channel`(价格通道线)
|
|
64
|
+
`gann_fan`(江恩角度线) `gann_percent`(百分比线) `wave`(波段线) 等 40 种,
|
|
65
|
+
完整映射见 [docs/FORMAT.md](docs/FORMAT.md)。
|
|
66
|
+
|
|
67
|
+
支持的周期:`1m 5m 15m 30m 60m day week month quarter year`。
|
|
68
|
+
|
|
69
|
+
## .eld 文件格式
|
|
70
|
+
|
|
71
|
+
每条画线 = 378 字节定长记录(小端),核心字段:
|
|
72
|
+
|
|
73
|
+
| 偏移 | 含义 |
|
|
74
|
+
|---|---|
|
|
75
|
+
| 0x00 | 市场(0=深 1=沪 2=京) |
|
|
76
|
+
| 0x01 | 6 位证券代码 |
|
|
77
|
+
| 0x18 | 指标页(如 `\x02STANDK`) |
|
|
78
|
+
| 0x56 / 0x5A | 起点 / 终点价格(float32) |
|
|
79
|
+
| 0x63 | 周期编码 |
|
|
80
|
+
| 0x65 | 画线类型 |
|
|
81
|
+
| 0x69 / 0x75 | 起点 / 终点日期(年 zone + 月日 9 位紧凑编码) |
|
|
82
|
+
| 0x6B / 0x77 | 起点 / 终点分钟数 |
|
|
83
|
+
| 0x71 | 个股内画线序号 |
|
|
84
|
+
| 0xD1 / 0xD5 / 0xD9 | 线宽 / 线型 / BGRA 颜色 |
|
|
85
|
+
|
|
86
|
+
完整字段表见 [docs/FORMAT.md](docs/FORMAT.md)。
|
|
87
|
+
|
|
88
|
+
## 运行测试
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pip install -e . pytest
|
|
92
|
+
pytest
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
测试基准包含一个**经通达信实测可导入**的真实样本文件(逐字节比对)。
|
|
96
|
+
|
|
97
|
+
## 免责声明
|
|
98
|
+
|
|
99
|
+
本项目仅做画线坐标数据与通达信画线文件格式之间的转换,不提供任何行情数据,
|
|
100
|
+
不构成任何投资建议。使用前请[阅读完整免责声明](DISCLAIMER.md)。
|
|
101
|
+
|
|
102
|
+
## 量化相关定制需求
|
|
103
|
+
|
|
104
|
+
承接画线自动化、分析系统 → 通达信画线管道、数据格式转换等定制开发,
|
|
105
|
+
联系方式见 [README 联系方式](#量化相关定制需求) 或 [Gitee Issues](https://gitee.com/mengyiheng/tdxdraw/issues)。
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
MIT
|
|
110
|
+
|
|
111
|
+
## 量化相关定制需求
|
|
112
|
+
|
|
113
|
+
作者承接画线自动化与通达信二次开发的定制开发:
|
|
114
|
+
|
|
115
|
+
| 方向 | 示例 |
|
|
116
|
+
|---|---|
|
|
117
|
+
| 🖍 画线工程化 | 分析系统信号 → 通达信画线管道、多周期批量落图、画线模板管理 |
|
|
118
|
+
| 📐 格式深水区 | 更多画线类型/样式区字段逆向、tdxline.dat 在线注入 |
|
|
119
|
+
| 🛠 私有部署 | 全市场批量导入、CI 自动生成画线、团队共享线组 |
|
|
120
|
+
|
|
121
|
+
**联系合作**:[Gitee Issues](https://gitee.com/mengyiheng/tdxdraw/issues) 或 [Gitee 私信](https://gitee.com/mengyiheng)。复杂需求请附:画线数据量级、时效要求、期望交付形式。
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tdxdraw"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "通达信画线文件(.eld)读写与批量生成工具,零依赖"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "mengyiheng" }]
|
|
13
|
+
keywords = ["tdx", "tongdaxin", "drawline", "eld", "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.scripts]
|
|
24
|
+
tdxdraw = "tdxdraw.cli:main"
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.packages.find]
|
|
27
|
+
where = ["src"]
|
tdxdraw-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""tdxdraw — 通达信画线文件(.eld)读写与批量转换。
|
|
2
|
+
|
|
3
|
+
典型流程:把任意来源的坐标清单(CSV / JSON / 你的分析程序输出)
|
|
4
|
+
转成通达信官方"画线导入"功能可识别的 .eld 文件。
|
|
5
|
+
|
|
6
|
+
from tdxdraw import Drawing, write_eld
|
|
7
|
+
|
|
8
|
+
d = Drawing("sh", "600519", "hline", 1250.0, 1250.0,
|
|
9
|
+
date(2026, 1, 5), date(2026, 10, 19), line_id=1)
|
|
10
|
+
write_eld("out.eld", [d]) # 通达信 K 线界面 -> 画线工具 -> 导入画线
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .batch import csv_to_eld, eld_to_csv, read_csv, read_json, write_csv, write_json
|
|
14
|
+
from .eld import (LINE_TYPES, PERIODS, RECORD_SIZE, Drawing,
|
|
15
|
+
decode, encode, read_eld, write_eld)
|
|
16
|
+
|
|
17
|
+
__version__ = "0.1.0"
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"Drawing", "encode", "decode", "read_eld", "write_eld",
|
|
21
|
+
"read_csv", "write_csv", "read_json", "write_json",
|
|
22
|
+
"csv_to_eld", "eld_to_csv",
|
|
23
|
+
"LINE_TYPES", "PERIODS", "RECORD_SIZE",
|
|
24
|
+
"__version__",
|
|
25
|
+
]
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""CSV / JSON 画线清单与 .eld 文件互转。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import csv
|
|
6
|
+
import json
|
|
7
|
+
from datetime import date, datetime
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import List
|
|
10
|
+
|
|
11
|
+
from .eld import Drawing, read_eld
|
|
12
|
+
|
|
13
|
+
FIELDS = ["market", "code", "line_type", "start_price", "end_price",
|
|
14
|
+
"start_date", "end_date", "start_time", "end_time",
|
|
15
|
+
"period", "line_id", "line_width", "color_bgr"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _parse_row(row: dict) -> Drawing:
|
|
19
|
+
d = dict(row)
|
|
20
|
+
for k in ("start_date", "end_date"):
|
|
21
|
+
d[k] = datetime.strptime(str(d[k]), "%Y-%m-%d").date()
|
|
22
|
+
for k in ("start_time", "end_time", "line_id", "line_width", "color_bgr"):
|
|
23
|
+
if k in d:
|
|
24
|
+
d[k] = int(d[k])
|
|
25
|
+
for k in ("start_price", "end_price"):
|
|
26
|
+
d[k] = float(d[k])
|
|
27
|
+
return Drawing(**{k: v for k, v in d.items() if k in FIELDS})
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def read_csv(path: str | Path) -> List[Drawing]:
|
|
31
|
+
with open(path, newline="", encoding="utf-8-sig") as f:
|
|
32
|
+
return [_parse_row(r) for r in csv.DictReader(f)]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def write_csv(path: str | Path, drawings: List[Drawing]) -> None:
|
|
36
|
+
with open(path, "w", newline="", encoding="utf-8-sig") as f:
|
|
37
|
+
w = csv.DictWriter(f, fieldnames=FIELDS)
|
|
38
|
+
w.writeheader()
|
|
39
|
+
for d in drawings:
|
|
40
|
+
w.writerow({k: getattr(d, k) for k in FIELDS})
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def read_json(path: str | Path) -> List[Drawing]:
|
|
44
|
+
items = json.loads(Path(path).read_text(encoding="utf-8"))
|
|
45
|
+
if isinstance(items, dict):
|
|
46
|
+
items = items.get("drawings", [])
|
|
47
|
+
return [_parse_row({k: str(v) for k, v in it.items()}) for it in items]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def write_json(path: str | Path, drawings: List[Drawing]) -> None:
|
|
51
|
+
items = [{k: getattr(d, k) for k in FIELDS} for d in drawings]
|
|
52
|
+
Path(path).write_text(json.dumps({"drawings": items}, ensure_ascii=False, indent=2),
|
|
53
|
+
encoding="utf-8")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def eld_to_csv(eld_path: str | Path, csv_path: str | Path) -> int:
|
|
57
|
+
ds = read_eld(eld_path)
|
|
58
|
+
write_csv(csv_path, ds)
|
|
59
|
+
return len(ds)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def csv_to_eld(csv_path: str | Path, eld_path: str | Path) -> int:
|
|
63
|
+
ds = read_csv(csv_path)
|
|
64
|
+
from .eld import write_eld
|
|
65
|
+
return write_eld(eld_path, ds)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""tdxdraw 命令行。
|
|
2
|
+
|
|
3
|
+
用法:
|
|
4
|
+
tdxdraw build in.csv out.eld # CSV/JSON 画线清单 -> 通达信 .eld
|
|
5
|
+
tdxdump 等价: tdxdraw export in.eld out.csv # .eld -> CSV
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from .batch import csv_to_eld, eld_to_csv, read_json
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def main(argv: list[str] | None = None) -> int:
|
|
17
|
+
a = list(sys.argv[1:] if argv is None else argv)
|
|
18
|
+
if len(a) != 3 or a[0] not in ("build", "export"):
|
|
19
|
+
print(__doc__)
|
|
20
|
+
return 1
|
|
21
|
+
cmd, src, dst = a
|
|
22
|
+
if cmd == "build":
|
|
23
|
+
n = csv_to_eld(src, dst) if src.lower().endswith(".csv") else _json_to_eld(src, dst)
|
|
24
|
+
print(f"[tdxdraw] {n} 条画线 -> {dst}(通达信: 画线工具 -> 导入画线)")
|
|
25
|
+
else:
|
|
26
|
+
n = eld_to_csv(src, dst)
|
|
27
|
+
print(f"[tdxdraw] {n} 条画线 -> {dst}")
|
|
28
|
+
return 0
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _json_to_eld(src: str, dst: str) -> int:
|
|
32
|
+
from .eld import write_eld
|
|
33
|
+
ds = read_json(src)
|
|
34
|
+
return write_eld(dst, ds)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
if __name__ == "__main__":
|
|
38
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""tdxdraw.eld — 通达信画线文件(.eld)记录编解码。
|
|
2
|
+
|
|
3
|
+
每条画线为固定 378 字节记录,格式经通达信官方"画线导入"功能实测验证。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import struct
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from datetime import date, datetime
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Iterable, List
|
|
13
|
+
|
|
14
|
+
RECORD_SIZE = 0x17A # 378
|
|
15
|
+
|
|
16
|
+
MARKETS = {"sz": 0, "sh": 1, "bj": 2}
|
|
17
|
+
|
|
18
|
+
PERIODS = {
|
|
19
|
+
"1m": 0x07, "5m": 0x00, "15m": 0x01, "30m": 0x02, "60m": 0x03,
|
|
20
|
+
"day": 0x04, "week": 0x05, "month": 0x06, "quarter": 0x0A, "year": 0x0B,
|
|
21
|
+
}
|
|
22
|
+
PERIOD_DECODE = {v: k for k, v in PERIODS.items()}
|
|
23
|
+
|
|
24
|
+
LINE_TYPES = {
|
|
25
|
+
"segment": 0x0A, "ray": 0x0B, "arrow": 0x21, "ray2": 0x22,
|
|
26
|
+
"hline": 0x26, "hray": 0x2D, "hsegment": 0x2E,
|
|
27
|
+
"vline": 0x27, "price_tag": 0x2A, "channel": 0x1C,
|
|
28
|
+
"parallel": 0x1D, "gann_percent": 0x0D, "wave": 0x0E,
|
|
29
|
+
"gann_price": 0x1F, "regression": 0x11,
|
|
30
|
+
"circle": 0x23, "rectangle": 0x17, "text": 0x16,
|
|
31
|
+
"gann_fan": 0x0C,
|
|
32
|
+
}
|
|
33
|
+
LINE_TYPE_DECODE = {v: k for k, v in LINE_TYPES.items()}
|
|
34
|
+
|
|
35
|
+
# 偏移量表(实测验证)
|
|
36
|
+
OFF = {
|
|
37
|
+
"market": 0x00, "code": 0x01, "indicator": 0x18,
|
|
38
|
+
"start_price": 0x56, "end_price": 0x5A, "period": 0x63,
|
|
39
|
+
"line_type": 0x65, "fixed12": 0x66, "start_date": 0x69,
|
|
40
|
+
"start_time": 0x6B, "start_price2": 0x6D, "line_id": 0x71,
|
|
41
|
+
"end_date": 0x75, "end_time": 0x77, "end_price2": 0x79,
|
|
42
|
+
"line_start": 0xCD, "line_width": 0xD1, "line_style": 0xD5,
|
|
43
|
+
"color": 0xD9, "draw_date": 0xE5,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# 年份 zone 编码表(zone: 0=1-4月, 1=5-10月, 2=11-12月)
|
|
47
|
+
_YEAR_ZONE_YO = {
|
|
48
|
+
(2024, 0): 83, (2024, 1): 81, (2024, 2): 82,
|
|
49
|
+
(2025, 0): 84, (2025, 1): 85, (2025, 2): 86,
|
|
50
|
+
(2026, 0): 88, (2026, 1): 89,
|
|
51
|
+
}
|
|
52
|
+
_YO_YEAR_ZONE = {v: k for k, v in _YEAR_ZONE_YO.items()}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _encode_eld_date(d: date) -> int:
|
|
56
|
+
md = d.month * 100 + d.day
|
|
57
|
+
zone = 0 if md <= 511 else (1 if md <= 1023 else 2)
|
|
58
|
+
yo = _YEAR_ZONE_YO.get((d.year, zone))
|
|
59
|
+
if yo is None:
|
|
60
|
+
yo = 80 + (d.year - 2024) * 4 + zone # 扩展规则(2027+)
|
|
61
|
+
return (yo << 9) | (md - zone * 512)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _decode_eld_date(v: int) -> date:
|
|
65
|
+
yo, stored_md = v >> 9, v & 0x1FF
|
|
66
|
+
year, zone = _YO_YEAR_ZONE.get(yo, (1941 + yo, 0))
|
|
67
|
+
md = stored_md + zone * 512
|
|
68
|
+
return date(year, md // 100, md % 100)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class Drawing:
|
|
73
|
+
"""一条画线。time 字段为当天分钟数(0-1439),日线一般填 900(=15:00)。"""
|
|
74
|
+
|
|
75
|
+
market: str # "sh" / "sz" / "bj"
|
|
76
|
+
code: str # 6 位代码
|
|
77
|
+
line_type: str # LINE_TYPES 的键
|
|
78
|
+
start_price: float
|
|
79
|
+
end_price: float
|
|
80
|
+
start_date: date
|
|
81
|
+
end_date: date
|
|
82
|
+
start_time: int = 900
|
|
83
|
+
end_time: int = 900
|
|
84
|
+
period: str = "day"
|
|
85
|
+
line_id: int = 1 # 个股内唯一序号(从 1 开始)
|
|
86
|
+
line_width: int = 1
|
|
87
|
+
color_bgr: int = 0x00FFFFFF # BGRA
|
|
88
|
+
indicator: bytes = b"\x02STANDK" # 画线所属指标页
|
|
89
|
+
|
|
90
|
+
def __post_init__(self) -> None:
|
|
91
|
+
self.validate()
|
|
92
|
+
|
|
93
|
+
def validate(self) -> None:
|
|
94
|
+
if self.market not in MARKETS:
|
|
95
|
+
raise ValueError(f"市场必须是 {list(MARKETS)}: {self.market}")
|
|
96
|
+
if not (self.code.isdigit() and len(self.code) == 6):
|
|
97
|
+
raise ValueError(f"代码必须是 6 位数字: {self.code}")
|
|
98
|
+
if self.line_type not in LINE_TYPES:
|
|
99
|
+
raise ValueError(f"未知画线类型: {self.line_type}")
|
|
100
|
+
if self.period not in PERIODS:
|
|
101
|
+
raise ValueError(f"未知周期: {self.period}")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def encode(d: Drawing) -> bytes:
|
|
105
|
+
"""Drawing -> 378 字节记录(通达信画线导入功能可直接识别)。"""
|
|
106
|
+
d.validate()
|
|
107
|
+
r = bytearray(RECORD_SIZE)
|
|
108
|
+
r[OFF["market"]] = MARKETS[d.market]
|
|
109
|
+
r[OFF["code"]:OFF["code"] + 6] = d.code.encode("ascii").ljust(6, b"\x00")
|
|
110
|
+
r[OFF["indicator"]:OFF["indicator"] + 7] = d.indicator[:7].ljust(7, b"\x00")
|
|
111
|
+
|
|
112
|
+
struct.pack_into("<f", r, OFF["start_price"], d.start_price)
|
|
113
|
+
struct.pack_into("<f", r, OFF["end_price"], d.end_price)
|
|
114
|
+
r[OFF["period"]] = PERIODS[d.period]
|
|
115
|
+
r[OFF["line_type"]] = LINE_TYPES[d.line_type]
|
|
116
|
+
r[OFF["fixed12"]] = 0x10
|
|
117
|
+
|
|
118
|
+
struct.pack_into("<H", r, OFF["start_date"], _encode_eld_date(d.start_date))
|
|
119
|
+
struct.pack_into("<H", r, OFF["start_time"], d.start_time)
|
|
120
|
+
struct.pack_into("<f", r, OFF["start_price2"], d.start_price)
|
|
121
|
+
|
|
122
|
+
lid = max(0, d.line_id - 1)
|
|
123
|
+
r[OFF["line_id"]:OFF["line_id"] + 3] = lid.to_bytes(3, "little")
|
|
124
|
+
|
|
125
|
+
struct.pack_into("<H", r, OFF["end_date"], _encode_eld_date(d.end_date))
|
|
126
|
+
struct.pack_into("<H", r, OFF["end_time"], d.end_time)
|
|
127
|
+
struct.pack_into("<f", r, OFF["end_price2"], d.end_price)
|
|
128
|
+
|
|
129
|
+
r[OFF["line_start"]] = 0x01
|
|
130
|
+
r[OFF["line_width"]] = d.line_width
|
|
131
|
+
r[OFF["line_style"]] = LINE_TYPES[d.line_type]
|
|
132
|
+
struct.pack_into("<I", r, OFF["color"], d.color_bgr)
|
|
133
|
+
|
|
134
|
+
now = datetime.now()
|
|
135
|
+
r[OFF["draw_date"]] = (now.day + 204) & 0xFF
|
|
136
|
+
r[OFF["draw_date"] + 1] = (now.month + 34) & 0xFF
|
|
137
|
+
r[OFF["draw_date"] + 2] = (now.year - 1973) & 0xFF
|
|
138
|
+
r[OFF["draw_date"] + 3] = 0x01
|
|
139
|
+
r[OFF["draw_date"] + 4] = 0x01
|
|
140
|
+
return bytes(r)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def decode(rec: bytes) -> Drawing:
|
|
144
|
+
"""378 字节记录 -> Drawing。"""
|
|
145
|
+
if len(rec) != RECORD_SIZE:
|
|
146
|
+
raise ValueError(f"记录长度必须为 {RECORD_SIZE} 字节: {len(rec)}")
|
|
147
|
+
return Drawing(
|
|
148
|
+
market={0: "sz", 1: "sh", 2: "bj"}.get(rec[OFF["market"]], "sh"),
|
|
149
|
+
code=rec[OFF["code"]:OFF["code"] + 6].rstrip(b"\x00").decode("ascii", "replace"),
|
|
150
|
+
line_type=LINE_TYPE_DECODE.get(rec[OFF["line_type"]], f"unknown_{rec[OFF['line_type']]:#04x}"),
|
|
151
|
+
start_price=struct.unpack_from("<f", rec, OFF["start_price"])[0],
|
|
152
|
+
end_price=struct.unpack_from("<f", rec, OFF["end_price"])[0],
|
|
153
|
+
start_date=_decode_eld_date(struct.unpack_from("<H", rec, OFF["start_date"])[0]),
|
|
154
|
+
end_date=_decode_eld_date(struct.unpack_from("<H", rec, OFF["end_date"])[0]),
|
|
155
|
+
start_time=struct.unpack_from("<H", rec, OFF["start_time"])[0],
|
|
156
|
+
end_time=struct.unpack_from("<H", rec, OFF["end_time"])[0],
|
|
157
|
+
period=PERIOD_DECODE.get(rec[OFF["period"]], f"p{rec[OFF['period']]:#04x}"),
|
|
158
|
+
line_id=int.from_bytes(rec[OFF["line_id"]:OFF["line_id"] + 3], "little") + 1,
|
|
159
|
+
line_width=rec[OFF["line_width"]],
|
|
160
|
+
color_bgr=struct.unpack_from("<I", rec, OFF["color"])[0],
|
|
161
|
+
indicator=rec[OFF["indicator"]:OFF["indicator"] + 7],
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def read_eld(path: str | Path) -> List[Drawing]:
|
|
166
|
+
"""读取 .eld 画线文件为 Drawing 列表。"""
|
|
167
|
+
data = Path(path).read_bytes()
|
|
168
|
+
n = len(data) // RECORD_SIZE
|
|
169
|
+
return [decode(data[i * RECORD_SIZE:(i + 1) * RECORD_SIZE]) for i in range(n)]
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def write_eld(path: str | Path, drawings: Iterable[Drawing]) -> int:
|
|
173
|
+
"""Drawing 列表 -> .eld 文件(通达信画线导入功能可直接加载)。"""
|
|
174
|
+
payload = b"".join(encode(d) for d in drawings)
|
|
175
|
+
Path(path).write_bytes(payload)
|
|
176
|
+
return len(payload) // RECORD_SIZE
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tdxdraw
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 通达信画线文件(.eld)读写与批量生成工具,零依赖
|
|
5
|
+
Author: mengyiheng
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: tdx,tongdaxin,drawline,eld,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
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
<p align="center"><img src="assets/cover.png" alt="tdxdraw" width="720"/></p>
|
|
20
|
+
|
|
21
|
+
# tdxdraw — 通达信画线批量生成与转换工具
|
|
22
|
+
|
|
23
|
+
把**任意来源的画线坐标清单**(CSV / JSON / 你的分析程序输出)批量转成通达信官方
|
|
24
|
+
「画线导入」功能可直接识别的 `.eld` 文件;也可把通达信导出的画线文件反向解析成
|
|
25
|
+
CSV / JSON,做二次处理。
|
|
26
|
+
|
|
27
|
+
- 📤 **生成**:程序化批量画线 → `.eld` → 通达信一键导入
|
|
28
|
+
- 📥 **解析**:通达信导出的 `.eld` → CSV / JSON,供回测、复盘、二次分析
|
|
29
|
+
- 🪶 **零第三方依赖**,Python 3.9+
|
|
30
|
+
- ✅ **逐字节校验**:编码结果与通达信实测可导入的样本文件逐字节一致
|
|
31
|
+
- 🧭 **格式完全开源**:378 字节记录结构文档化于 [docs/FORMAT.md](docs/FORMAT.md)
|
|
32
|
+
|
|
33
|
+
## 安装
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install tdxdraw
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 快速开始
|
|
40
|
+
|
|
41
|
+
### 1. 写一个画线清单(CSV)
|
|
42
|
+
|
|
43
|
+
```csv
|
|
44
|
+
market,code,line_type,start_price,end_price,start_date,end_date,period,line_id
|
|
45
|
+
sh,600519,hline,1250.00,1250.00,2026-01-05,2026-10-19,day,1
|
|
46
|
+
sh,600519,segment,1181.64,1367.85,2026-06-12,2026-09-30,day,2
|
|
47
|
+
sz,002805,rectangle,1391.60,1321.62,2026-08-27,2026-09-15,day,1
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 2. 转成 .eld 并导入通达信
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
tdxdraw build drawings.csv mylines.eld
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
通达信 K 线界面 → 画线工具 → **画线管理(或导入画线)** → 选择 `mylines.eld`,
|
|
57
|
+
所有画线立即出现在对应个股的 K 线图上。
|
|
58
|
+
|
|
59
|
+
### 3. 也可以只用 Python API
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from datetime import date
|
|
63
|
+
from tdxdraw import Drawing, write_eld, read_eld
|
|
64
|
+
|
|
65
|
+
d = Drawing("sh", "600519", "hline", 1250.0, 1250.0,
|
|
66
|
+
date(2026, 1, 5), date(2026, 10, 19), line_id=1)
|
|
67
|
+
write_eld("mylines.eld", [d])
|
|
68
|
+
|
|
69
|
+
bars = read_eld("通达信导出.eld") # 反向解析
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 4. 反向导出为 CSV
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
tdxdraw export 通达信导出.eld lines.csv
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## 支持的画线类型
|
|
79
|
+
|
|
80
|
+
`segment`(线段) `hline`(水平直线) `ray`(直线) `arrow`(箭头) `rectangle`(矩形)
|
|
81
|
+
`text`(文字注释) `circle`(圆) `parallel`(平行直线) `channel`(价格通道线)
|
|
82
|
+
`gann_fan`(江恩角度线) `gann_percent`(百分比线) `wave`(波段线) 等 40 种,
|
|
83
|
+
完整映射见 [docs/FORMAT.md](docs/FORMAT.md)。
|
|
84
|
+
|
|
85
|
+
支持的周期:`1m 5m 15m 30m 60m day week month quarter year`。
|
|
86
|
+
|
|
87
|
+
## .eld 文件格式
|
|
88
|
+
|
|
89
|
+
每条画线 = 378 字节定长记录(小端),核心字段:
|
|
90
|
+
|
|
91
|
+
| 偏移 | 含义 |
|
|
92
|
+
|---|---|
|
|
93
|
+
| 0x00 | 市场(0=深 1=沪 2=京) |
|
|
94
|
+
| 0x01 | 6 位证券代码 |
|
|
95
|
+
| 0x18 | 指标页(如 `\x02STANDK`) |
|
|
96
|
+
| 0x56 / 0x5A | 起点 / 终点价格(float32) |
|
|
97
|
+
| 0x63 | 周期编码 |
|
|
98
|
+
| 0x65 | 画线类型 |
|
|
99
|
+
| 0x69 / 0x75 | 起点 / 终点日期(年 zone + 月日 9 位紧凑编码) |
|
|
100
|
+
| 0x6B / 0x77 | 起点 / 终点分钟数 |
|
|
101
|
+
| 0x71 | 个股内画线序号 |
|
|
102
|
+
| 0xD1 / 0xD5 / 0xD9 | 线宽 / 线型 / BGRA 颜色 |
|
|
103
|
+
|
|
104
|
+
完整字段表见 [docs/FORMAT.md](docs/FORMAT.md)。
|
|
105
|
+
|
|
106
|
+
## 运行测试
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
pip install -e . pytest
|
|
110
|
+
pytest
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
测试基准包含一个**经通达信实测可导入**的真实样本文件(逐字节比对)。
|
|
114
|
+
|
|
115
|
+
## 免责声明
|
|
116
|
+
|
|
117
|
+
本项目仅做画线坐标数据与通达信画线文件格式之间的转换,不提供任何行情数据,
|
|
118
|
+
不构成任何投资建议。使用前请[阅读完整免责声明](DISCLAIMER.md)。
|
|
119
|
+
|
|
120
|
+
## 量化相关定制需求
|
|
121
|
+
|
|
122
|
+
承接画线自动化、分析系统 → 通达信画线管道、数据格式转换等定制开发,
|
|
123
|
+
联系方式见 [README 联系方式](#量化相关定制需求) 或 [Gitee Issues](https://gitee.com/mengyiheng/tdxdraw/issues)。
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT
|
|
128
|
+
|
|
129
|
+
## 量化相关定制需求
|
|
130
|
+
|
|
131
|
+
作者承接画线自动化与通达信二次开发的定制开发:
|
|
132
|
+
|
|
133
|
+
| 方向 | 示例 |
|
|
134
|
+
|---|---|
|
|
135
|
+
| 🖍 画线工程化 | 分析系统信号 → 通达信画线管道、多周期批量落图、画线模板管理 |
|
|
136
|
+
| 📐 格式深水区 | 更多画线类型/样式区字段逆向、tdxline.dat 在线注入 |
|
|
137
|
+
| 🛠 私有部署 | 全市场批量导入、CI 自动生成画线、团队共享线组 |
|
|
138
|
+
|
|
139
|
+
**联系合作**:[Gitee Issues](https://gitee.com/mengyiheng/tdxdraw/issues) 或 [Gitee 私信](https://gitee.com/mengyiheng)。复杂需求请附:画线数据量级、时效要求、期望交付形式。
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/tdxdraw/__init__.py
|
|
5
|
+
src/tdxdraw/__main__.py
|
|
6
|
+
src/tdxdraw/batch.py
|
|
7
|
+
src/tdxdraw/cli.py
|
|
8
|
+
src/tdxdraw/eld.py
|
|
9
|
+
src/tdxdraw.egg-info/PKG-INFO
|
|
10
|
+
src/tdxdraw.egg-info/SOURCES.txt
|
|
11
|
+
src/tdxdraw.egg-info/dependency_links.txt
|
|
12
|
+
src/tdxdraw.egg-info/entry_points.txt
|
|
13
|
+
src/tdxdraw.egg-info/top_level.txt
|
|
14
|
+
tests/test_eld.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tdxdraw
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from tdxdraw import Drawing, decode, encode, read_eld, write_eld
|
|
7
|
+
|
|
8
|
+
FIXTURE = Path(__file__).parent / "fixtures" / "tdx_accepted_001.eld"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_decode_real_tdx_accepted_file():
|
|
12
|
+
"""通达信官方画线导入功能实测认得的样本,必须能正确解码。"""
|
|
13
|
+
(d,) = read_eld(FIXTURE)
|
|
14
|
+
assert d.market == "sz"
|
|
15
|
+
assert d.code == "002805"
|
|
16
|
+
assert d.line_type == "segment"
|
|
17
|
+
assert d.start_price == pytest.approx(22.20, abs=0.01)
|
|
18
|
+
assert d.end_price == pytest.approx(21.19, abs=0.01)
|
|
19
|
+
assert d.start_date == date(2026, 6, 12)
|
|
20
|
+
assert d.line_id == 3
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_reencode_matches_tdx_accepted_bytes():
|
|
24
|
+
"""重编码应与通达信可接受的原始字节逐字节一致(画线创建日期除外)。"""
|
|
25
|
+
raw = FIXTURE.read_bytes()
|
|
26
|
+
mine = bytearray(encode(decode(raw)))
|
|
27
|
+
mine[0xE5:0xEA] = raw[0xE5:0xEA] # draw_date 按写入时刻生成
|
|
28
|
+
assert bytes(mine) == raw
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_encode_decode_roundtrip():
|
|
32
|
+
d = Drawing("sh", "600519", "hline", 1250.0, 1250.0,
|
|
33
|
+
date(2026, 1, 5), date(2026, 10, 19), line_id=7)
|
|
34
|
+
back = decode(encode(d))
|
|
35
|
+
assert back.market == "sh" and back.code == "600519"
|
|
36
|
+
assert back.line_type == "hline"
|
|
37
|
+
assert back.start_price == pytest.approx(1250.0)
|
|
38
|
+
assert back.start_date == date(2026, 1, 5)
|
|
39
|
+
assert back.end_date == date(2026, 10, 19)
|
|
40
|
+
assert back.line_id == 7
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_eld_file_roundtrip(tmp_path):
|
|
44
|
+
ds = [
|
|
45
|
+
Drawing("sz", "000001", "segment", 10.0, 11.5,
|
|
46
|
+
date(2026, 3, 2), date(2026, 4, 8), line_id=1),
|
|
47
|
+
Drawing("sh", "600519", "rectangle", 1391.6, 1321.62,
|
|
48
|
+
date(2026, 8, 27), date(2026, 9, 30), line_id=2),
|
|
49
|
+
]
|
|
50
|
+
p = tmp_path / "out.eld"
|
|
51
|
+
assert write_eld(p, ds) == 2
|
|
52
|
+
assert p.stat().st_size == 2 * 378
|
|
53
|
+
back = read_eld(p)
|
|
54
|
+
assert len(back) == 2
|
|
55
|
+
for a, b in zip(back, ds):
|
|
56
|
+
assert a.market == b.market and a.code == b.code
|
|
57
|
+
assert a.line_type == b.line_type
|
|
58
|
+
assert a.start_price == pytest.approx(b.start_price, abs=0.01)
|
|
59
|
+
assert a.end_price == pytest.approx(b.end_price, abs=0.01)
|
|
60
|
+
assert (a.start_date, a.end_date, a.line_id) == (b.start_date, b.end_date, b.line_id)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_validation():
|
|
64
|
+
with pytest.raises(ValueError):
|
|
65
|
+
Drawing("xx", "600519", "hline", 1, 1, date(2026, 1, 1), date(2026, 1, 1))
|
|
66
|
+
with pytest.raises(ValueError):
|
|
67
|
+
Drawing("sh", "60051", "hline", 1, 1, date(2026, 1, 1), date(2026, 1, 1))
|
|
68
|
+
with pytest.raises(ValueError):
|
|
69
|
+
Drawing("sh", "600519", "noline", 1, 1, date(2026, 1, 1), date(2026, 1, 1))
|