资讯动态

通达信.day文件解析与pytdx本地数据管线搭建:量化回测的可靠数据源

发布时间:2026/10/3 4:46:10 来源:尧图企业网站定制
做量化的朋友应该都有这种感觉策略写起来不难难的是搞到干净、完整、可复现的历史数据。我早年习惯从各种数据平台倒数据不是格式混乱就是限流严重免费接口常常隔三差五封IP付费接口又贵得离谱。后来我干脆回头研究自己电脑上装着的通达信——每天收盘后它都会在本地落盘一批.day文件里面就是全市场股票的日线行情。更妙的是Python生态里有个叫pytdx的库既能直接解析这些本地文件又能连接通达信行情服务器拉网络数据几分钟就能搭出一条稳定的本地数据管线。下面我就把这个过程完整拆开讲从二进制格式原理、pytdx代码实战到批量读取和踩坑记录全部摊开。适合刚入门量化、不想被数据API绑架的朋友直接抄作业也适合已经在写回测、但被数据质量折腾得头大的老手参考。1. 先把三件事搞清楚.day文件、pytdx和本地数据管线1.1 通达信本地数据到底存在哪里如果你用的是PC版通达信安装目录下一般会有一个vipdoc文件夹里面的结构大概是这样的vipdoc/ ├── sh/ │ └── lday/ │ ├── sh600000.day │ ├── sh600036.day │ └── ... ├── sz/ │ └── lday/ │ ├── sz000001.day │ └── ...sh是上海市场sz是深圳市场lday是日线目录文件名是市场前缀6位股票代码.day。比如sh600000.day就是浦发银行的日线数据sz000001.day就是平安银行的日线数据。除了日线vipdoc下面还有存分钟线的目录比如fzline、minline但后缀各不相同。现在我们重点处理的就是这些后缀为.day的文件。为什么通达信要把数据落地成文件因为它自己也要读。每次你打开软件看K线它本质上就是去读这些本地文件然后渲染到界面上。也就是说这些文件里的数据和你眼睛看到的历史K线完全一致。既然软件自己能读那我们当然也能用Python去读。这里有个很实用的排查技巧如果你在自己电脑上找不到vipdoc目录用系统的全盘文件搜索功能搜一下.day后缀基本都能定位到因为通达信不管装在哪个盘日线数据目录的名称和结构相对固定。1.2 pytdx这个库本事比大多数人以为的大很多人听说过pytdx是因为它经常被用来连接通达信行情服务器、拉实时数据。这确实是最常见的用法但pytdx的能力其实分成两块。第一块是hq模块对应行情查询走的是通达信协议连上服务器之后可以拉K线、五档、分时数据。第二块是reader模块对应本地读取直接解析通达信落盘的数据文件包括我们这篇文章要讲的.day文件还有分钟线、财务数据文件。也就是说pytdx一个库把网络拉取和本地解析都覆盖了。很多教程只讲了前者搞得大家都以为它只能联网用。实际上TdxDailyBarReader这个类两行代码就能把.day文件读出来根本不需要自己折腾二进制解析。不过话说回来底层格式解析也要懂。只有明白了.day文件里每个字节的含义你才知道pytdx读出来的数据靠不靠谱遇到缓存文件损坏、字段顺序变化时才不会两眼一抹黑。所以我后面第2章会先讲清楚文件格式第3章再给pytdx的现成用法两条路都走一遍你在实际项目里才能灵活切换。1.3 有现成的数据API为什么还要折腾本地.day如果你只是需要最近几百根K线做技术指标演示那各大平台的免费API完全够用。但如果是认真做量化回测本地.day文件有几个优点是云上API很难替代的。第一没有数量和频率限制。免费行情接口一般都限制单次拉取数量和请求频率有的拉完800根K线就得歇一会儿频繁请求还容易封IP。本地.day文件不存在这个问题整个市场几千只股票的历史日线就在你的硬盘里躺着想读多少读多少。第二数据是原始价可复现性最好。网络接口因为各家处理复权的方式不同同一段历史K线在不同平台可能长得不一样。而.day文件里存的是不复权的原始成交价格复权逻辑你可以自己在回测框架里统一处理从根上保证了数据的可复现性。这对策略回测来说特别重要数据源不一致回测结果就没有可比性。第三离线可用、速度快。本地文件读的是磁盘解析百万条记录也就秒级完全不用等网络。很多实盘环境在隔离网段连不了外网这时候本地.day文件几乎是唯一靠谱的历史数据来源。我自己就遇到过线上服务器不能访问外部行情接口的情况当时全靠定期同步过去的.day文件撑起了整个回测流程。2. .day文件的二进制格式32字节定长记录的秘密2.1 单条记录的结构拆解.day文件不是文本文件而是一个纯二进制文件没有表头没有分隔符。它的组织形式可以理解为一个固定宽度的表格每一行数据固定占32字节每个字段占4字节。通达信协议系列文件基本都是这种风格好处是读取极其高效坏处是——如果不对照着字段表去看就是一堆乱码。单条记录32字节的字段布局如下字节偏移长度类型字段说明04int32date日期整数格式YYYYMMDD44float32open开盘价元84float32high最高价元124float32low最低价元164float32close收盘价元204float32amount成交额元244int32volume成交量284int32reserved保留字段这里有两个容易搞错的地方。第一所有多字节整数和浮点数都是小端序存储也就是低字节在前Windows上的通达信按这个字节序写入所以解析时格式串里一定要加。第二成交量字段的单位在不同资料里说法不一我实际对比过默认是按股存的界面上显示的手通常是用这个数除以100得到的。你把本地文件解析出来的volume和通达信软件上的成交量对一下单位是啥立刻就清楚了不同券商版本可能存在细微差异。2.2 从文件大小反推记录数因为每条记录严格32字节所以一个.day文件里有多少条K线可以直接从文件大小算出来记录数 文件大小 / 32比如sh600000.day文件刚好是5440字节那就说明里面有170条日线记录。这个特性在调试的时候特别好用。如果你手工解析出来的记录数和这个公式对不上那基本可以断定解析逻辑有问题或者文件本身被截断过。利用这个特性我写了一个很小的手工解析函数只用Python标准库的struct模块就够了import struct def parse_day_file(filepath): records [] with open(filepath, rb) as f: while True: chunk f.read(32) if len(chunk) 32: break date, open_, high, low, close, amount, volume, _ struct.unpack(IfffffII, chunk) records.append({ date: date, open: open_, high: high, low: low, close: close, amount: amount, volume: volume, }) return recordsstruct格式串IfffffII展开来说就是小端序无符号整型I存日期连续5个浮点型f存开盘、最高、最低、收盘和成交额再跟两个无符号整型I存成交量和保留字段。这里日期用无符号整型是为了和通达信写入时的uint32对齐反正日期不会为负用I比用i更严谨。2.3 边界情况与隐蔽的大坑手工解析.day文件有几个边界情况如果不提前处理早晚会踩进去。第一个是文件尾部半条记录。断网断电、软件强制退出都可能让.day文件最后残留几个不完整的字节。所以循环读取时必须判断读出来的chunk长度是否等于32不足就直接跳出否则struct.unpack会直接抛异常。第二个是除权日的数据。通达信在除权日当天记录的原始价会包含除权跳空直接拿来做指标计算没问题但如果你要做回测撮合就得自己再算复权因子。这个不算bug但很多新手第一次看到K线上突然出现一个巨大缺口时会以为数据坏了其实这是正常现象。第三个是全市场文件名前缀的坑。上海市场的文件名以sh开头深圳市场以sz开头这一点看似简单但如果你写批量扫描脚本时不加区分单纯按代码去匹配很容易把sh600000和sz600000这种压根不存在的文件搞混。批量读取时建议直接把文件名的market前缀一起解析出来存成一个字段后面做数据筛选会方便很多。3. pytdx实战两行代码读取.day文件3.1 安装与版本注意pytdx是一个比较老牌的库安装很简单pip install pytdx pandaspytdx的依赖很少基本装上就能用。我建议在Python 3.8以上的环境里跑再老的环境虽然也能装但有些第三方依赖链容易出问题。pandas不是pytdx的硬依赖但解析完数据总得有个像样的数据结构去存所以干脆一起装上。安装完之后验证一下导入路径是否正确from pytdx.reader import TdxDailyBarReader如果这一步报ModuleNotFoundError大概率是装到了别的环境里。用which python确认一下当前解释器或者pip list | grep pytdx看看版本到底装没装上。还有一个比较隐蔽的问题某些Python环境里同时存在多个通达信相关库比如pytdx和easyquotation它们的模块名可能有冲突如果导入时报的不是ModuleNotFoundError而是奇怪的属性错误先看看是不是和别的库重名了。3.2 最小可用代码直接读出所有日线TdxDailyBarReader的用法非常直接下面这段代码可以在几秒钟内读完整个文件from pytdx.reader import TdxDailyBarReader reader TdxDailyBarReader(rC:\new_tdx\vipdoc\sh\lday\sh600000.day) count reader.get_bar_count() bars reader.get_bars(count)get_bar_count()返回的是这个文件里有几条K线get_bars(count)则是把前count条数据全部取出来。返回的bars是一个二维结构每一行对应一条K线字段顺序和.day文件里的物理布局基本一致也就是date、open、high、low、close、amount、volume这一串。拿到bars之后我习惯立刻转成pandas的DataFrame后面做筛选、合并、落库都方便import pandas as pd df pd.DataFrame(bars, columns[date, open, high, low, close, amount, volume]) df[date] pd.to_datetime(df[date], format%Y%m%d) df df.sort_values(date).reset_index(dropTrue) print(df.tail())这里有个小提示如果你装的是比较新的pytdx版本bars的列数或者列顺序可能和我的假设有出入别慌先print(bars[:2])看一眼原始结构再调整columns列表就行。不同版本之间确实有过列顺序调整的情况我自己就在升级之后遇到过列对不上的问题排查方法很简单打印出第一条记录对照前面的字段表一个个核对就行。3.3 批量扫描整个vipdoc目录单只股票肯定满足不了回测需求。实际项目中我更习惯写一个批量扫描函数把一个市场目录下所有的.day文件都读出来统一合并成一张大表import os import pandas as pd from pytdx.reader import TdxDailyBarReader def batch_read_day_files(day_dir): frames [] for root, _, files in os.walk(day_dir): for name in files: if not name.lower().endswith(.day): continue filepath os.path.join(root, name) code os.path.splitext(name)[0] try: reader TdxDailyBarReader(filepath) count reader.get_bar_count() if count 0: continue bars reader.get_bars(count) df pd.DataFrame( bars, columns[date, open, high, low, close, amount, volume] ) df[code] code frames.append(df) except Exception as exc: print(f{filepath} 解析失败: {exc}) continue if not frames: return pd.DataFrame() return pd.concat(frames, ignore_indexTrue)几点实操心得。第一单只股票的.day文件一般不大但几千个文件合在一起就有点规模了建议第一次全量生成后直接存成Parquet或者SQLite之后只做增量更新不要每次回测都从.day文件从头解析。第二except里的日志一定要打全路径因为批量读取时某一个文件损坏你不会想在全市场几千个文件名里猜是哪只股票出问题。第三concat之前先确认每个df的列名完全一致否则pandas会给你拼出一堆NaN列。3.4 本地数据和网络数据交叉验证本地文件读出来之后怎么确认它没坏最直接的办法是和通达信行情服务器上的数据做交叉对比。pytdx的hq模块可以做这件事from pytdx.hq import TdxHq_API api TdxHq_API() servers [ (119.147.212.81, 7709), (124.71.187.122, 7709), ] df_net None for host, port in servers: try: api.connect(host, port) bars api.get_security_bars(9, 0, 000001, 0, 100) df_net api.to_df(bars) break except Exception as exc: print(f{host}:{port} 连接失败: {exc}) api.disconnect() continue if df_net is not None: print(df_net.tail())get_security_bars的第一个参数9代表日K线第二个参数0代表深圳市场第三个参数是股票代码。因为网络接口每次最多返回800根所以这里只取最后100根做抽样对比。拿本地文件的最后100条记录和df_net按日期对齐比较收盘价是否一致如果完全一致基本可以确认本地数据没问题。不一致的话优先检查是不是复权设置导致的价格差异再看看本地文件最后更新日期和服务器上最新交易日是否相同。公开的通达信行情服务器地址网上有很多上面这两个是我实测还算稳定的。连不上的时候不要死磕一个地址把候选列表放在数组里轮询是更务实的做法。4. 从零手写解析器理解原理才能玩出花儿4.1 一次性读入内存再切片性能更好上一节我们用了pytdx的两行代码直接读文件方便是方便但对为什么能读出来这件事还是有点黑盒。所以这一章我们自己写一个更完整的解析器顺便把性能优化也做了。前面手工解析用的是while循环反复read(32)这种方式逻辑简单但文件大了以后性能一般因为每次read都要发生一次系统调用。更聪明的做法是一口气把整个文件读进内存然后按32字节的步长去切片这样系统调用的次数从记录数降到了1次整体速度快一个量级。import struct from pathlib import Path class DayFileParser: RECORD_SIZE 32 FORMAT IfffffII def __init__(self, filepath: str): self.filepath Path(filepath) def parse(self): data self.filepath.read_bytes() total len(data) // self.RECORD_SIZE records [] for i in range(total): chunk data[i * self.RECORD_SIZE:(i 1) * self.RECORD_SIZE] date, open_, high, low, close, amount, volume, _ struct.unpack( self.FORMAT, chunk ) records.append({ date: date, open: open_, high: high, low: low, close: close, amount: amount, volume: volume, }) return records如果你对性能有更高的要求可以再进一步用numpy的frombuffer直接把整个文件映射成一个结构化数组import numpy as np dt np.dtype([ (date, u4), (open, f4), (high, f4), (low, f4), (close, f4), (amount, f4), (volume, u4), (reserved, u4), ]) def parse_day_file_numpy(filepath): data Path(filepath).read_bytes() arr np.frombuffer(data, dtypedt) return arr这个版本几乎没有任何Python层面的逐条循环百万条记录的解析也只需要几百毫秒非常夸张。arr返回之后直接arr[date]、arr[close]这样按列访问后面再转成DataFrame也很快。这个方法很值得放进自己的工具库里以后解析各种固定宽度的二进制文件都能复用不一定非要和通达信绑定。4.2 数据完整性校验自己写了解析器就多了一个别人没有的环节校验。我强烈建议在日线数据进入回测库之前至少做两道校验。第一道是OHLC逻辑校验。合法的K线必须满足high max(open,close)low min(open,close)如果某条记录high比open和close都低或者low比它们都高那这条数据一定有问题不是文件损坏就是解析时字段对错了位。第二道是文件大小校验。拿文件大小除以32如果不能整除说明文件头部或尾部有残留字节。对残留部分我通常选择直接丢弃而不是报错因为通达信自己读这些文件时遇到的也是同样的情况截断到最后一个完整记录反而是最兼容的做法。下面是一段简单的校验函数def validate_records(records): errors [] for i, r in enumerate(records): if r[high] max(r[open], r[close]) - 1e-6: errors.append((i, high小于open/close)) if r[low] min(r[open], r[close]) 1e-6: errors.append((i, low大于open/close)) return errors校验可能发现的问题很多时候不是文件坏了而是你解析时取了错误的字节偏移。比如把成交量字段当成价格来读出来的数据就会乱七八糟OHLC校验一秒就能揪出来。4.3 增量更新的思路最后聊一下怎么把.day文件持续用起来。通达信每天收盘后会更新当天的K线到本地文件所以你的本地数据仓库也应该跟着增量更新。一个比较省事的做法是记录每个.day文件的大小和修改时间mtime每天跑定时任务时只重新解析那些大小变化了或者mtime变了的文件。因为.day文件是不断追加的日期靠前的那部分历史数据不会变每次全量重读几千个文件纯属浪费磁盘和CPU。def needs_update(filepath, cached_size, cached_mtime): stat filepath.stat() return stat.st_size ! cached_size or int(stat.st_mtime) ! cached_mtime配合上一节的批量解析函数先在本地存一份manifest路径、大小、mtime、解析后的数据落库位置每天只需要几十毫秒就能判断出哪些文件需要重新读取。这个思路对做日频、周频回测的朋友特别实用既省时间又不容易出错。5. 常见问题与排查技巧实录5.1 struct.error: unpack requires a buffer of 32 bytes这是手工解析时最常见的报错原因基本只有一个文件尾部残留了不足32字节的半个记录。解决办法有两种。第一种是在循环里判断chunk长度小于32就break第二种是像前面那样先按文件大小除以32得到完整记录数然后只解析前total条。我个人推荐第二种因为先算条数还能顺手统计文件是否被截断过。不过要注意如果把整个文件一次性读进内存再切片要小心data[i*32:(i1)*32]的切片范围。Python切片在越界时不会报错只会返回更短的字节串然后struct.unpack就会炸。解决方案就是在循环前先算出total len(data) // 32严格按这个范围取。5.2 读出来的K线条数比自己预期少如果你发现某只股票明明上市十几年解析出来的日K却只有几百条先别怀疑解析代码很可能是两个原因。第一通达信默认仅下载最近N天数据或者你从来没完整下载过。这种情况需要到通达信菜单里找到盘后数据下载把日线数据完整下载一遍.day文件才会补齐到全量历史。第二你读的文件根本不是主图的日线而是某个指标计算后生成的临时文件路径或者后缀看错了。建议打开文件属性看一眼文件大小结合文件大小/32记录数这个公式马上就能判断数据量够不够。还有一种情况比较隐蔽有的券商定制版通达信会把数据目录放在自定义位置比如安装在D盘但数据目录可能在C盘用户目录下。如果你在安装目录下找不到.day文件去别的盘搜一下vipdoc可能会豁然开朗。5.3 网络接口拉到的数据不全只有800根pytdx连行情服务器拉K线时get_security_bars单次最多返回800根很多新手不知道这个限制以为只能拿到最近800天的数据。实际上可以通过循环翻页的方式把整段历史分批拉完def fetch_all_daily(api, market, code): frames [] start 0 while True: bars api.get_security_bars(9, market, code, start, 800) if not bars: break frames.append(api.to_df(bars)) if len(bars) 800: break start 800 return pd.concat(frames, ignore_indexTrue)start参数表示从哪根K线开始取每次取800根直到返回不足800根说明已经到最早期数据。这样拼出来的数据虽然也能用但速度慢、有频率限制如果只是为了拿历史数据做回测还是本地.day文件更靠谱。5.4 数据直接存CSV好还是存Parquet/SQLite好这个问题很多朋友问过我。早期我图省事全市场日线一股脑导出成一个大CSV结果文件好几个GB每次读进来都要卡半天后来老老实实换成了Parquet。我的建议是CSV只适合导出小批量数据给人看不适合做全量存储。Parquet压缩率高、读起来快还天然保留数据类型推荐作为主力存储格式。如果不想引入额外依赖SQLite也是不错的选择查询按股票代码或日期范围非常灵活写起来也简单。但不管存什么格式都不要把它放在和.day文件同一个盘上备份策略上至少保留两份副本否则通达信哪天抽风重装覆盖了vipdoc你会非常被动。最后再分享一点个人体会。这套pytdx读本地.day 网络接口抽检的组合我已经在项目里跑了大半年白天收盘后定时任务自动跑增量更新晚上回测框架直接读本地数据全程稳定、免费、可控再也没有被哪家数据API的限流策略折腾过。如果你只是想快速验证一个策略想法直接用TdxDailyBarReader读单只股票就够了但如果你打算认真搭建自己的回测数据管线我强烈建议从第二章的手写解析器开始把底层的文件格式吃透。这样后续无论是排查数据异常还是对接其他通达信衍生数据比如分钟线、财务数据都会顺手得多。另外再多说一句通达信升级或重装时可能会覆盖甚至清空vipdoc目录。怕丢数据的话提前把整个vipdoc文件夹做个备份或者想办法让通达信把数据目录指到别的盘。这个问题我踩过一次现在每次升级前都会习惯性看一眼本地数据还在不在。数据是量化的根基数据没了策略写得再漂亮也是白搭。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑