Skip to content

donge/tdx_ext

Repository files navigation

tdx_ext — 通达信数据 DuckDB 扩展

用 C++ 编写的 DuckDB 扩展,可直接通过 SQL 读取通达信(TDX)股票行情数据文件(.lc1.lc5.day)。

功能特点

  • 支持三种通达信文件格式:1 分钟线.lc1)、5 分钟线.lc5)、日线.day
  • 通过文件扩展名自动识别格式,无需手动指定
  • 单一 SQL 表函数 read_tdx('文件路径'),接口简洁
  • 基于 C_STRUCT ABI,兼容 Python duckdb pip 包(v1.4.4),无需额外动态库
  • 输出标准 OHLCV 列,datetime 列类型为 TIMESTAMP

输出列

列名 类型 说明
datetime TIMESTAMP 时间(分钟线含时分,日线为当天 00:00)
open FLOAT 开盘价
high FLOAT 最高价
low FLOAT 最低价
close FLOAT 收盘价
volume BIGINT 成交量(股)
amount DOUBLE 成交额(元)

文件格式说明

三种格式均为32 字节/记录、小端序、无文件头的二进制文件。

.lc1 / .lc5(1 分钟 / 5 分钟线)

偏移 大小 类型 含义
0 2 uint16 日期:(年-2004)×2048 + 月×100 + 日
2 2 uint16 时间:从午夜起的分钟数(如 571 = 09:31)
4 4 float32 开盘价
8 4 float32 最高价
12 4 float32 最低价
16 4 float32 收盘价
20 4 float32 成交额(元)
24 4 uint32 成交量(股)
28 4 uint32 未知字段(已忽略)

.day(日线)

偏移 大小 类型 含义
0 4 uint32 日期:YYYYMMDD 整数
4 4 uint32 开盘价 × 100(如 338569 = 3385.69)
8 4 uint32 最高价 × 100
12 4 uint32 最低价 × 100
16 4 uint32 收盘价 × 100
20 4 float32 成交额(元)
24 4 uint32 成交量(股)
28 4 uint32 未知字段(已忽略)

环境要求

  • macOS(Apple Silicon / arm64)
  • CMake ≥ 3.15
  • Python 3 + duckdb pip 包 v1.4.4
  • DuckDB v1.4.4 开发库(头文件 + libduckdb.dylib

构建步骤

1. 下载 DuckDB v1.4.4 开发库

mkdir -p /tmp/duckdb_dev_144
cd /tmp/duckdb_dev_144
# 下载 macOS universal 包
curl -L https://github.com/duckdb/duckdb/releases/download/v1.4.4/libduckdb-osx-universal.zip -o libduckdb.zip
unzip libduckdb.zip

2. 构建并注入元数据(一键脚本)

git clone https://github.com/donge/tdx_ext.git
cd tdx_ext
./build_and_inject.sh

脚本会自动完成:

  1. CMake 编译扩展(.so.duckdb_extension
  2. 向扩展二进制末尾注入 512 字节 DuckDB 元数据 footer(平台、ABI 类型等)

构建产物:build/tdx.duckdb_extension

3. 手动构建(可选)

cmake -S . -B build
cmake --build build
python3 build_and_inject.sh  # 重新注入 footer

使用方法

import duckdb

db = duckdb.connect(config={"allow_unsigned_extensions": True})
db.execute("LOAD 'build/tdx.duckdb_extension'")

# 读取 1 分钟线
df = db.execute("SELECT * FROM read_tdx('/path/to/sh000001.lc1')").df()

# 读取 5 分钟线
df = db.execute("SELECT * FROM read_tdx('/path/to/sh000001.lc5')").df()

# 读取日线
df = db.execute("SELECT * FROM read_tdx('/path/to/sh000001.day')").df()

# 支持完整 SQL 查询
result = db.execute("""
    SELECT
        strftime(datetime, '%Y-%m') AS month,
        AVG(close)                  AS avg_close
    FROM read_tdx('/path/to/sh000001.day')
    WHERE datetime >= '2024-01-01'
    GROUP BY month
    ORDER BY month
""").fetchall()

测试

python3 test_tdx.py

测试脚本会验证:

  • 扩展正常加载
  • 输出列名和类型与预期一致
  • 三种格式的记录数与预期相符
  • 打印样本行和日期范围

项目结构

tdx_ext/
├── src/
│   ├── tdx_extension.cpp      # 扩展主体(C_STRUCT ABI,表函数实现)
│   ├── tdx_reader.cpp         # 文件类型检测
│   └── include/
│       ├── tdx_extension.hpp  # 扩展头文件(占位)
│       └── tdx_reader.hpp     # TdxFileType 枚举 + DetectTdxFileType 声明
├── CMakeLists.txt             # 独立 CMake 构建(指向 /tmp/duckdb_dev_144)
├── build_and_inject.sh        # 一键构建 + 注入元数据脚本
└── test_tdx.py                # 功能测试脚本

技术说明

为什么用 C_STRUCT ABI?

Python duckdb pip 包将 DuckDB 静态编译_duckdb.cpython-*.so,不导出任何 DuckDB C++ 符号。因此:

  • CPP ABI(依赖 C++ vtable 符号)无法使用
  • C_STRUCT ABI:DuckDB 在加载扩展时通过 duckdb_extension_access::get_api() 将所有 C API 函数指针打包成 duckdb_ext_api_v1 结构体传入,扩展通过该结构体调用所有 DuckDB 功能,无需在 dlopen 时解析任何 DuckDB 符号

入口函数签名:

bool tdx_init_c_api(duckdb_extension_info info, duckdb_extension_access *access);

关键实现细节

  • access->get_api() 返回的指针仅在 tdx_init_c_api() 调用期间有效(指向调用栈上的临时对象)。因此在初始化时必须将整个结构体复制到全局变量,而不是仅保存指针。
  • macOS 构建使用 -undefined dynamic_lookup,运行时符号由宿主进程(Python 解释器内的 DuckDB)提供。
  • 扩展二进制需在末尾附加 512 字节元数据 footer(8 个字段 × 32 字节,逆序存储 + 256 字节签名),并以 allow_unsigned_extensions: True 加载。

License

MIT

About

通达信(TDX)股票行情数据 DuckDB 扩展,支持 .lc1/.lc5/.day 三种格式,SQL 接口 read_tdx()

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors