架构总览¶
分层¶
调用链是单向的,每一层只依赖它下面的一层——想加新接口时先想清楚「这属于哪一层」。
flowchart TD
A["api.py — TdxData<br/>唯一对外入口,28 个统一方法 + 33 个废弃别名"] --> B["router.py<br/>按命令选通道"]
B --> C["pool/ — 动态连接池<br/>标准池 / MAC 池 / EX 池"]
B --> D["scheduler/ — 并发分页调度<br/>Paginator"]
C --> E["protocol/ — 协议层<br/>帧 / 变长整数 / 价格 / 日期 / 命令"]
E --> F["TCP 通达信服务器<br/>7709 标准+MAC · 7727 EX"]
A --> G["cache/ — 双层 TTL 缓存<br/>Memory LRU → Disk SQLite"]
A --> H["codec/ — 解析与序列化<br/>缓存载荷 / 服务器文件"]
H --> I["models/ — dataclass + 枚举"]
目录¶
src/pytdxdata/
├── api.py # 统一接口 TdxData(对外唯一入口)
├── symbols.py # 标的解析:字符串 → Symbol(parse_symbol / format_symbol / market_targets)
├── router.py # 标准 / MAC / EX 通道选路
├── config.py # 服务器清单加载 + 选路优先级链 + 用户级测速缓存路径
├── indicator.py # 技术指标(纯函数,无依赖)
├── server_probe.py # 服务器探测,可 python -m 运行
├── protocol/ # 协议层
│ ├── frame.py # 标准帧
│ ├── mac_frame.py # MAC 帧
│ ├── _binary.py # 变长整数等底层字节工具
│ ├── price.py # 自定义浮点价格还原
│ ├── volume.py # 成交量差分还原
│ ├── datetime_.py # 日期 / 时间编码
│ └── commands/ # 16 个命令模块
├── pool/ # 连接池
│ ├── connection.py # 单连接(含重连)
│ ├── pool.py # 标准 / MAC 动态池
│ ├── ex_pool.py # EX 池
│ ├── health.py # 健康分与选路
│ └── probe_cache.py # 当日全量测速 + 用户级缓存(后台、无感)
├── scheduler/
│ └── paginator.py # 并发分页调度(3 连接并行)
├── cache/
│ ├── key.py # 缓存键规范化
│ ├── ttl.py # TTL 分级策略
│ ├── memory.py # 内存 LRU
│ ├── disk.py # 磁盘 SQLite(WAL)
│ └── manager.py # 两级协同 + single-flight
├── models/ # dataclass + 枚举(单文件,无子模块)
└── codec/ # 缓存载荷序列化 + 服务器文件解析
├── payload.py # 缓存载荷 pickle + zlib
├── block.py # 板块 .dat
├── industry.py # tdxhy.cfg
└── financial.py # gpcw.txt / gpcw*.zip
一次 get_bars 的完整路径¶
sequenceDiagram
participant U as 调用方
participant A as api.py
participant C as cache/
participant R as router.py
participant P as pool/
participant S as scheduler/
U->>A: get_bars("sh600000", DAY, count=2400)
A->>A: 规范化缓存键
A->>C: 查缓存
alt 命中
C-->>A: list[SecurityBar]
A-->>U: 直接返回(0 网络 0 解析)
else 未命中
A->>C: 登记 single-flight
A->>R: 选通道
R-->>A: 标准通道
A->>S: 分页(count=2400 → 3 页)
S->>P: 并发取 3 个连接
P->>P: 健康分 + EWMA 延迟选路
P-->>S: 3 个连接
S->>S: 并行拉取 → 按序拼接
S-->>A: 完整 bars
A->>C: 写入内存 + 磁盘
A-->>U: 返回
end
关键点:
- 缓存查在选路之前——命中就完全不走网络,所以重复请求是 0.016s 量级
- single-flight 在缓存层——同一个键的并发请求会合并成一次回源,其余等待结果
- 分页与选路分离——
scheduler只管拆分与拼接,不知道通道差异;pool只管给连接
三个设计约束¶
| 约束 | 原因 |
|---|---|
| 零运行时依赖 | 用 socket / zlib / struct / sqlite3 / asyncio 全部搞定,避免依赖地狱和版本冲突 |
返回 list[dataclass] |
不让调用方被 pandas 绑架;需要 DataFrame 自己转一行 |
| API 向后兼容 | 一个数据类型收敛为一个跨市场统一方法;旧方法名保留为 deprecated 别名(1.0 移除),老代码在兼容窗口内仍可用 |