200 行 Lua,DuckDB 就能读通达信行情了
网上有篇文章,叫「用 DuckDB 读通达信数据文件」。作者写了个 C 扩展,实现了 read_tdx() 函数,让 .lc1、.lc5、.day 这些通达信行情文件可以直接用 SQL 查。
项目很实用,但有个门槛——C 扩展得编译,得处理 DuckDB 的 C_STRUCT ABI,踩坑不少。作者自己也说:「ABI 不对就崩」「存指针不如复制结构体」。
我就在想,如果把这个功能用 Lua 写,会是什么样?
● ● ●
200 行
两个小时后,结果出来了。
libs/datasource/tdx.lua,200 行。零编译,零依赖,luajit_module(mode := 'install', sql_name := 'tdx') 一条 SQL 装上就能用。
对比一下 C 版本:
C 扩展: tdx_extension.cpp+tdx_reader.cpp+tdx_reader.hpp,三个文件,~400 行,需要 CMake + vcpkg 编译链Lua 版本:一个文件,200 行,纯 Lua + LuaJIT FFI(只有 float32 转字节用到了 FFI)
● ● ●
通达信文件格式
先说格式。通达信的行情文件很简单,三种格式,都是 32 字节一条记录,小端序,没有文件头。
日线(.day),32 字节:
分钟线(.lc1 / .lc5),也是 32 字节:
● ● ●
最麻烦的部分
写解析器本身不复杂——string.byte() 读字节,套公式算日期,LuaJIT FFI 把 4 个字节转成 float32。
最麻烦的是日期解码。通达信的日期编码很"土":
日线:YYYYMMDD 整数,直接除 10000 取整 分钟线:(年-2004)×2048 + 月×100 + 日,时间从午夜算分钟数
都不难,但第一次写的人得对着 hexdump 猜半天。C 扩展的作者也说了:「格式全靠猜,32 字节一条记录,小端序,没有文件头,只能 hexdump 对着软件显示的价格一个字段一个字段猜。」
● ● ●
怎么用
装好 duckdb-luajit 扩展后,一条 SQL 装上:
SELECT * FROM luajit_module(mode := 'install', sql_name := 'tdx');然后查数据:
SELECT split_part(val,'|',1) AS datetime,
split_part(val,'|',2)::FLOAT AS open,
split_part(val,'|',3)::FLOAT AS high,
split_part(val,'|',4)::FLOAT AS low,
split_part(val,'|',5)::FLOAT AS close,
split_part(val,'|',6)::BIGINT AS volume,
split_part(val,'|',7)::DOUBLE AS amount
FROM luajit_table('tdx', list := '/path/sh000001.day');
输出格式是 pipe 分隔的字符串,datetime|open|high|low|close|volume|amount。分钟线的时间精确到秒,日线只到天。
查最高收盘价:
SELECT MAX(split_part(val,'|',5)::FLOAT) AS max_close
FROM luajit_table('tdx', list := '/path/sh000001.day');
● ● ●
为什么用 Lua 写
DuckDB 的扩展生态目前分成两派:
C 扩展性能好,但要编译、要处理 ABI 兼容、要打包成 .duckdb_extension 二进制。Python duckdb 的 pip 包是静态编译的,不导出任何 C++ 符号,加载 C 扩展就得用 C_STRUCT ABI,文档几乎没有,全靠翻源码。
Lua 扩展(duckdb-luajit)不用编译,一条 SQL 从仓库拉下来就能用。200 行的解析器,如果用 C 写,光 CMakeLists.txt 和 vcpkg.json 就占了一半行数。
不是说 C 不好——性能场景当然用 C。但通达信文件解析是 I/O 密集型,瓶颈在磁盘,CPU 那点差异可以忽略。用 Lua 省掉的编译时间、部署成本、ABI 调试,比那点性能差划算得多。
● ● ●
代码
代码在 duckdb-luajit-libs 仓库的 libs/datasource/tdx.lua:
github.com/alitrack/duckdb-luajit-libs
MIT 协议,随便用。