ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Python解析通达信本地数据:.day日线与.blk板块文件实战

2026/10/3 10:20:46 拓冰建站 浏览量
Python解析通达信本地数据:.day日线与.blk板块文件实战 1. 前言为什么非要从通达信里扒数据如果你跟我一样平时用通达信看盘、复盘又习惯了用Python做数据分析那大概率会有同一个想法能不能把通达信本地保存的行情数据和板块数据直接读出来统一交给pandas去处理我最初的需求很简单——手动从一个自选股板块里复制几十只股票的日K数据再粘贴进表格来回折腾实在太蠢了。后来我试着直接用通达信的数据导出功能但导出格式和字段数量都有限制用起来不够顺。于是干脆把目标转向了通达信安装目录下的本地数据文件也就是我在这篇文章里要讲的.day日线数据文件很多时候大家习惯叫它dat数据和.blk板块文件。先说结论这两类文件格式并不复杂日线数据是固定的32字节一条的二进制记录板块文件本质上是GBK编码的文本文件用Python标准库里的struct和open就能搞定不需要装任何解析库。我把完整源码整理成了可以直接跑的脚本你只需要改一下通达信的安装路径就能把本地所有股票的K线数据和板块成员信息批量导出来。这篇文章适合谁如果你正在做量化策略回测、想做全市场选股扫描、或者单纯想把自己的通达信自选股变成一份结构化清单那这篇内容可以直接拿来用。我不光讲怎么解析还会把我踩过的几个坑——字节对齐搞错导致价格翻倍、中文乱码、文件路径搞混——全部列出来附上排查思路让你少走弯路。2. 动手前的功课通达信本地文件到底存在哪2.1 安装目录里的数据地图通达信本身是一个典型的本地行情软件所有历史K线数据都会缓存到你电脑上目录结构大概长这样行情数据根目录通常是通达信安装目录下的vipdoc上海市场日线vipdoc\sh\sh600000.day深圳市场日线vipdoc\sz\sz000001.day北京市场相关数据vipdoc\bj\不过北交所早期数据的形式和沪深略有差异个别券商版本可能没有独立目录分钟线数据vipdoc\sh\fzline\或vipdoc\sh\lc5\sh600000.lc55分钟线、lc11分钟线不同版本目录名略有差异板块文件目录一般在T0002\blocknew\或T0002\block\里面能看到blk_自选股.blk这类文件第一眼看到这么多目录别慌。我们只需要抓住两类重点一类是股票代码加后缀构成的文件名比如sh600000.day另一类是blk_*.blk的板块定义文件。通达信的股票行情数据以“市场代码”作为文件名主干市场代码sh、sz、bj表意非常直观所以解析时可以直接从文件名反推股票代码。有个细节要注意通达信默认安装到哪个盘、哪个目录不同券商的定制版差别很大。有些版本把数据放在安装目录下有些版本放在用户目录下的T0002文件夹。建议打开通达信安装目录看一眼或者直接在安装目录里搜索sh600000.day很快就能定位到正确的数据根目录。2.2 日线.day文件32字节一条的二进制记录很多资料里把通达信的本地行情文件统称为dat文件实际上严格来说日线数据文件的后缀是.day5分钟线是.lc51分钟线是.lc1不过它们底层都是二进制记录格式。通达信日线文件的结构固定为每条记录32字节字段排列顺序如下偏移量长度类型字段说明04字节int32日期格式为YYYYMMDD例如2024011944字节int32开盘价实际价格 整数值 / 10084字节int32最高价实际价格 整数值 / 100124字节int32最低价实际价格 整数值 / 100164字节int32收盘价实际价格 整数值 / 100204字节float32成交额单位元244字节int32成交量单位手284字节int32保留字段一般无意义忽略文件读取逻辑就变得非常简单用os.path.getsize算出文件总字节数除以32得到记录条数然后循环读取每条记录的32字节用struct.unpack解包即可。实际测试中这个格式在多只股票、多个券商版本上都是稳定的。唯二需要留神的地方一是价格整数是乘以100后存储的比如收盘价12.34元存的是1234解析后不除以100会差100倍二是文件末尾偶尔会有不足32字节的残留数据通常是通达信运行时正在写入导致的解析时按字节数整除32取整即可末尾残缺直接丢弃。2.3 .blk板块文件文本格式与GBK编码板块文件是个纯文本文件只是编码用的是GBK不是常见的UTF-8。打开方式很简单结构也简单第一行板块名称第二行开始每行一只股票格式通常是“股票代码|股票名称”行与行之间用换行符分隔每行末尾可能有回车换行符比如一个自选股板块文件内容大概是自选股 600000|浦发银行 000001|平安银行 600519|贵州茅台如果直接用文本编辑器打开乱码或者用Python默认的UTF-8编码读取报错基本都是编码问题。由于通达信运行在Windows环境下中文文本默认使用GBK/GB2312编码Python里读取时指定encodinggbk就能正常解析。不过不同券商的通达信版本对blk文件格式的处理并不完全一致。有的版本第一行板块名后面会带多余空格有的用\t分隔字段还有的板块文件可能直接存放成“代码 名称”的空格分隔形式。所以解析代码最好兼容多种分隔符我后面给的源码里已经做了处理。3. 核心代码来了Python解析通达信日线数据3.1 struct模块解析二进制的最小套路Python读取二进制文件最常用的就是struct模块。它做的事情简单说就是“按照指定格式把一段字节流拆成一个个字段”。对通达信日线文件来说每条记录的格式字符串可以写成import struct # 表示小端字节序 # i 表示有符号32位整数 # f 表示32位浮点数 record_format iiiiifii这条格式串的含义是依次读取日期、开盘、最高、最低、收盘、成交额、成交量、保留字段。其中前5个是32位有符号整数第6个是32位浮点数后面2个又是有符号整数。一个iiiiifii正好对应32字节。有两点要提醒你第一不能省略通达信在x86架构的Windows上生成数据默认小端存储显式写出可以防止将来在Mac或Linux上出现字节序解析错误第二价格相关的字段我用的是有符号整数i而不是无符号整数I正常数据当然是正数但用有符号数可以避免个别异常值被错误解析成超大的正数。3.2 解析日线文件的完整函数下面这个函数可以直接复制使用输入一个.day文件路径返回一个字典列表每条记录包含日期、开高低收、成交额和成交量。import os import struct from datetime import datetime def parse_day_file(filepath): 解析通达信日线.day文件 返回: list[dict]每个dict包含日期、开高低收、成交额、成交量 record_format iiiiifii record_size struct.calcsize(record_format) # record_size 一定等于 32 with open(filepath, rb) as f: raw_data f.read() total_len len(raw_data) valid_count total_len // record_size records [] for i in range(valid_count): start i * record_size chunk raw_data[start:start record_size] (date_int, open_int, high_int, low_int, close_int, amount, vol, _) struct.unpack(record_format, chunk) records.append({ date: date_int, datetime: datetime.strptime(str(date_int), %Y%m%d), open: open_int / 100.0, high: high_int / 100.0, low: low_int / 100.0, close: close_int / 100.0, amount: amount, vol: vol }) return records用法很简单day_file rD:\tdx\vipdoc\sh\sh600000.day data parse_day_file(day_file) print(data[-5:]) # 打印最近5条记录我自己在Windows和macOS上都跑过这个函数同样的文件解析结果一致唯一要注意的是文件路径里的反斜杠在Windows下容易引发转义问题建议统一用原始字符串r...或者正斜杠/。3.3 日期与价格的换算细节日期字段的本质是整数比如20240119。把它直接当成整数保存在DataFrame里也没问题但如果你要按时间序列排序、画图最好转成真正的日期对象。datetime.strptime(str(date_int), %Y%m%d)是最省事的写法但要注意这个转换在大批量数据时会有一些性能开销。如果只是排序和筛选直接用整数日期反而更快结果也更稳定。价格换算容易出错的地方在于“除不尽”。比如乘以100后存储实际价格可能是12.34解析后1234 / 100.0 12.34浮点数计算不会丢精度因为两位小数的除法在二进制浮点中依然精确表示。但如果你把价格当成float类型去读或者偏移量错一位解析出的价格就会变成天价或者负数。这是新手最容易踩的坑后面排查部分我会再详细讲。成交量单位也值得注意。通达信日线文件的成交量单位是“手”1手等于100股。在做资金流分析和换手率计算时要记得和股本数据统一单位。成交额字段的单位是元比如某天成交2.5亿元解析出来的amount大约是250000000.0。3.4 分钟线数据的扩展解析如果你不想只拿日线还想解析5分钟或1分钟线格式也类似只是字段略有不同。通达信的分钟线文件同样每条32字节字段顺序是偏移量长度类型字段04字节int32日期 YYYYMMDD44字节int32时间 HHMM例如0945表示09:4584字节float32开盘价124字节float32最高价164字节float32最低价204字节float32收盘价244字节float32成交额284字节int32成交量注意这里价格字段全部是float不再是整数乘100。原因在于分钟线数据量更大通达信在生成时直接使用浮点存储方便处理。如果你用解析日线的方式读分钟线把前四个float当成整数了价格同样会乱了套。所以解析前一定要根据文件后缀选择不同的格式串。4. 板块文件.blk解析与中文乱码处理4.1 编码问题的根源Windows简体中文环境下的通达信文本文件默认使用GBK编码保存。Python的open()函数如果不指定编码在Windows上可能默认使用系统编码但如果你在Mac或Linux上运行默认编码是UTF-8直接读取就会出现UnicodeDecodeError。即使不报错中文板块名和股票名称也可能变成一堆乱码。所以读取blk文件优先尝试gbk解码。考虑到个别新版本通达信可能改用了UTF-8我的源码里做了兼容处理先用GBK读如果报错再回退到UTF-8。4.2 解析blk文件的完整函数def parse_blk_file(filepath): 解析通达信.blk板块文件 返回: (板块名称, 股票列表) 股票列表元素为 (code, name) content None for encoding in [gbk, utf-8]: try: with open(filepath, r, encodingencoding) as f: content f.read() break except UnicodeDecodeError: continue if content is None: raise ValueError(f无法识别文件编码: {filepath}) lines [line.strip() for line in content.splitlines() if line.strip()] if not lines: return , [] block_name lines[0] members [] for line in lines[1:]: # 兼容 代码|名称、代码 名称、代码\t名称 三种分隔形式 code name if | in line: parts line.split(|) code parts[0].strip() name parts[1].strip() if len(parts) 1 else else: parts line.split() if len(parts) 1: code parts[0].strip() if len(parts) 2: name .join(parts[1:]).strip() if code: members.append((code, name)) return block_name, members调用示例blk_file rD:\tdx\T0002\blocknew\blk_自选股.blk block_name, stocks parse_blk_file(blk_file) print(block_name, stocks)这个函数返回的不只是板块成员列表还带回了板块名称。前面我们说过blk文件第一行就是板块名所以用它来做目录索引很方便。比如你自建了十几个板块用循环把所有blk文件都解析一遍就能得到一个完整的“板块 - 个股列表”字典。4.3 把板块和行情数据联动起来解析出blk文件后用途立刻变得丰富。我最常做的事是读取“自选股”板块的股票代码再去vipdoc目录下找对应的.day文件把整个板块所有股票的K线数据合并成一个DataFrame做统一分析。联动逻辑其实就三步从blk文件拿到股票代码列表根据代码前缀判断市场拼出对应的行情文件名如sh600000.day调用parse_day_file逐个解析最后合并需要注意一个细节通达信板块文件里存的股票代码可能是6位原始代码也可能是带市场前缀的代码比如SH600000。我在解析时没有强制转格式因为不同板块文件形式不统一。推荐在联动之前做个标准化处理统一转换成类似sh600000的小写格式这样可以直接匹配vipdoc目录下的文件名。下面是简单示例def normalize_code(code): 将板块文件中的股票代码标准化为 市场代码 的文件命名形式 code code.strip().lower() if code.startswith((sh, sz, bj)): return code if code.startswith((6, 9, 5)): return sh code if code.startswith((0, 2, 3)): return sz code if code.startswith((4, 8)): return bj code return code这个函数只是一个经验规则不能保证覆盖所有特殊情况但在绝大多数沪深A股的板块文件上是够用的。如果你发现某只股票匹配不上再手动检查一下它的真实市场归属即可。5. 从单文件到全市场批量扫描与落地5.1 扫描目录批量解析所有股票手动一个个文件去解析太傻了直接用os.walk或者os.listdir遍历整个vipdoc目录把所有.day文件全部解析完才是正确姿势。下面这段代码会扫描上海、深圳、北京三个市场目录返回一个嵌套字典第一层是市场第二层是股票代码值是记录列表。def scan_all_day_files(tdx_root): 扫描通达信数据根目录下所有日线文件 tdx_root: 通达信安装目录或数据根目录 返回: {sh: {600000: [records...]}, sz: {...}, bj: {...}} result {} for market in [sh, sz, bj]: market_dir os.path.join(tdx_root, vipdoc, market) if not os.path.isdir(market_dir): continue market_data {} for fname in os.listdir(market_dir): if not fname.endswith(.day): continue # 文件名形如 sh600000.day code fname[2:8] filepath os.path.join(market_dir, fname) try: market_data[code] parse_day_file(filepath) except Exception as e: print(f解析失败: {filepath}, 错误: {e}) if market_data: result[market] market_data return result这段代码里的try/except不是摆设。批量解析时个别文件可能损坏、可能正在写入、可能格式特殊异常捕获能保证整个扫描不中断而且还可以把出错的路径打出来方便后续排查。5.2 用pandas整理成DataFrame解析出的原始数据结构是“每只股票一个字典列表”直接做分析不太方便。我习惯的做法是把所有股票的数据压平成一个长表每一行代表某只股票在某一天的K线数据。这种长表结构做数据筛选、groupby、透视都非常顺手。import pandas as pd def build_dataframe(all_data): 把 scan_all_day_files 的返回结果转换成长表DataFrame rows [] for market, stocks in all_data.items(): for code, records in stocks.items(): for rec in records: rows.append({ code: code, market: market, date: rec[date], open: rec[open], high: rec[high], low: rec[low], close: rec[close], amount: rec[amount], vol: rec[vol] }) df pd.DataFrame(rows) if not df.empty: df df.sort_values([code, date]).reset_index(dropTrue) return df生成的DataFrame可以非常方便地做后续筛选比如只看贵州茅台600519最近一年数据、计算某只股票20日均线、或者按日期统计全市场的上涨家数。只要你有清晰的分析需求结构化之后的路就宽了。5.3 导出CSV或者Excel搞定DataFrame之后导出就是一行代码的事df.to_csv(all_day_data.csv, indexFalse, encodingutf-8-sig)这里强烈建议用utf-8-sig编码简单说就是给CSV文件加上UTF-8 BOM头。这样Excel直接双击打开才不至于中文乱码算是一个讨巧但非常实用的小细节。如果你要保留多只股票的单独文件也可以用groupby循环写入不同CSV但一般长表就够用了。5.4 增量更新的思路通达信的本地日线数据会随着你每天打开软件而不断追加。最简单粗暴的方式是每天全量重新解析一遍。全市场几千只股票解析一遍实测在普通笔记本上也就十几秒到几十秒完全可以接受。但如果你的股票数量特别多或者行情历史特别长想更快一点可以考虑增量更新记录每个.day文件的大小和最后修改时间如果文件大小没变化说明没有新增数据直接复用缓存如果文件变大了只读取新增的那段字节从上次文件大小到当前文件大小的偏移量开始读增量更新的实现也不复杂核心就是记录文件大小和偏移量。但要注意一点通达信在交易过程中写入数据时文件末尾经常会出现一条未写完的残记录直接读增量可能读到半个记录。稳妥做法是每次读取后仍然按32字节对齐判断最后一小段不足32字节就直接丢弃。我自己的经验是如果只是做日线分析全量解析简单可靠没必要为了省那几秒引入复杂度。6. 实战中踩过的坑问题速查表现象可能原因解决方案解析出来的价格是实际的100倍忘记把整数除以100所有开高低收字段解析后除以100.0价格正常但成交额、成交量对不上字段顺序搞混或格式串错误严格按date/open/high/low/close/amount/vol/reserved顺序解包中文板块名乱码用UTF-8读取了GBK文件指定encodinggbk读取文件末尾多出几个字节通达信正在写入数据按 total_len // 32 丢弃多余字节股票文件找不到代码前缀和文件目录不匹配先判断市场前缀 sh/sz/bj再拼路径解析某些股票时报错文件损坏或券商定制格式差异用 try/except 跳过并打印文件路径北交所数据为空部分版本没有bj目录检查是否需要单独下载历史数据这些坑我基本上都踩过一遍尤其是字节格式串写错导致价格离谱的问题。格式串里iiiiifii我一开始写成了iiiiifii看起来差不多但解析结果完全紊乱。建议拿到新格式文件时先用记事本确认下文件字节数再手动打开一只自己熟知的股票比如贵州茅台对照当天实际价格检查解析结果。只有样本验证通过才说明格式串真的写对了。另一个很有迷惑性的问题是通达信不同版本、不同券商定制的文件可能字段顺序一致但保留位不同。日线文件的最后4个字节是保留字段有的版本可能存了换手率或者复权因子但对我后续分析没影响直接忽略就好。7. 数据落地后的扩展玩法写到这一个完整的“通达信文件解析工具”已经成型了。解析出日线和板块数据只是一个起点后面可以做很多有意思的事。比如我一直想做的全市场“板块强度”扫描每天收盘后解析所有板块文件把每个板块内的成分股当天涨幅做均值再和指数对比看看哪些板块在悄悄走强。这里的数据基础就是blk板块文件加上各股的日线数据。再比如把历史日线导入回测框架。我试过直接用通达信的数据算MA20、MACD这些常规指标用pandas几行代码就搞定比在通达信公式编辑器里写舒服得多。做完策略回测后再把目标股票清单整理成新的blk文件导回通达信查看这套闭环打通之后效率提升非常明显。关于源码文章里给出的都是可直接运行的核心函数。如果你需要把整个目录扫描、板块解析、数据落地合成一个独立脚本只需要把scan_all_day_files、parse_blk_file、build_dataframe三个函数拼在一起再在if __name__ __main__里加几行入口逻辑即可。整个脚本不依赖第三方库除了可选pandasPython 3.8以上版本都能跑。最后分享一个小经验解析这类本地数据最忌讳的就是一次性想做完所有事。先写一个最小可用版本——只解析单只股票、只打印最近5条记录——确认格式无误后再一步步扩展成批量工具。这样即使踩坑也能快速定位问题是出在格式理解还是代码逻辑上。