ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

pytdx解析通达信.day文件:A股历史行情数据本地化实战

pytdx解析通达信.day文件:A股历史行情数据本地化实战 做A股量化的人迟早都会遇到一个问题去哪里搞一份靠谱、完整、还能离线跑的历史行情数据。我刚开始做Python量化交易的时候也迷信各种在线数据API后来发现限频、缺字段、历史深度不够都是常态最麻烦的是每次回测都要联网策略还没跑明白数据源先挂了。后来我开始研究通达信本地生成的.day文件再用pytdx这个库配合着做解析和读取整个数据链路一下就顺了。如果你是做A股日线级别策略、又不想被各种付费API绑住手脚这篇文章应该能帮到你。我会把.day文件的二进制格式、pytdx的用法、完整解析代码、以及我在实际操作中踩过的坑一次性讲清楚基本上看完就能直接抄作业。1. 为什么本地.day文件才是回测的最高性价比数据源1.1 在线API看着方便用起来全是坑先聊聊我为什么放着现成的在线数据不用非得去折腾本地文件。很多人觉得现在数据API那么多调个接口不就行了实际上等你真开始做策略回测问题就一个接一个冒出来。第一是限频。免费的数据API大多有每分钟请求次数限制全市场5000多只股票你批量拉日线拉不了几十只就被限流了。第二是历史深度。很多免费接口只提供最近两三年数据做长周期回测根本不够看。第三是字段口径不统一。有的接口返回前复权有的返回不复权有的甚至前后对不上拿来做回测结果跟实际交易完全不是一回事。我印象最深的一次某在线接口在盘中拉数据时居然返回了空的成交量字段导致策略信号突然失效。从那次以后我就养成了一个习惯任何策略回测前先把需要的历史行情落盘到本地用本地数据跑通以后再谈别的。而在本地数据源里通达信收盘后自动生成的.day文件是最容易获取、覆盖率也最高的选择。1.2 day文件到底是什么去哪找通达信软件每天收盘后会把当天全部A股的日K线数据写进本地文件这就是.day文件。它存放在安装目录下的vipdoc文件夹里按市场分成sh和sz两个子目录再往下是lday目录。具体路径大概是这样的通达信安装目录/vipdoc/sh/lday/sh600000.day 通达信安装目录/vipdoc/sz/lday/sz000001.day文件命名的规则很简单市场前缀加六位股票代码。sh代表上交所sz代表深交所。比如sh600000.day就是浦发银行的日线数据sz000001.day就是平安银行的日线数据。要注意的是通达信不同版本目录结构可能略有差异但vipdoc这个核心目录基本都在只要你的软件正常用过本地一定会留下这些文件。这个目录的价值在于它是通达信官方客户端每天自动更新的你不用额外写爬虫、不用付费买接口只需要定期把解析逻辑跑一遍就能拿到一份覆盖全市场的历史日线数据而且这些数据完全离线可用。2. pytdx库能干什么实时行情和本地解析一把抓2.1 安装和基础连接方式pytdx是Python连接通达信协议的一个三方库虽然网上很多人只拿它来拉实时行情但实际上它也内置了本地数据解析的reader模块。安装非常简单pip install pytdx安装完以后可以先连接一个通达信行情服务器验证一下库能不能正常工作。这里用标准的TdxHq_API来连接from pytdx.hq import TdxHq_API api TdxHq_API() with api.connect(119.147.212.81, 7709): # 类别传9表示日线市场0表示上证600000是股票代码 # 这里拉浦发银行最近10根日K线 bars api.get_security_bars(9, 0, 600000, 0, 10) df api.to_df(bars) print(df.head())connect的时候需要填行情服务器的IP和端口不同服务器的连通性会有一点差别建议多试几个选响应快的用。这个连接过程走的是通达信的公开行情协议和我们日常看行情软件是一样的没有额外的门槛。2.2 在线拉取日线数据和.day文件做对照用上面的代码拉下来的数据字段大概是这样的year month day open close high low vol amount 2023 12 1 9.50 9.60 9.65 9.45 123456.0 1.18e09 2023 12 4 9.58 9.55 9.62 9.50 110230.0 1.05e09可以看到在线接口返回的字段里open、close、high、low已经是我们熟悉的股票价格了vol是成交量amount是成交额。这些数据可以作为解析.day文件之后的对照参考后面会用到。不过这里要提醒一句在线接口返回的日线数据默认是不复权的。如果你在通达信软件里看的是前复权K线那和这里的结果会不一样。这个点非常关键后面讲坑的时候会展开。3. .day文件的二进制格式拆解一条记录32字节3.1 文件内容就是定长记录的堆叠.day文件本身不是文本文件而是一个二进制文件。它没有任何文件头就是一条一条定长记录从头排到尾。每一条记录固定32字节对应一根日K线。也就是说你只要知道这个文件里有多少条记录就可以直接计算有多少根K线。这32字节的排列方式如下偏移字节长度字段类型说明04日期uint32格式为YYYYMMDD例如2023120144开盘价uint32实际价格需要除以10084最高价uint32实际价格需要除以100124最低价uint32实际价格需要除以100164收盘价uint32实际价格需要除以100204成交额float32单位通常是元244成交量uint32需要除以100单位通常是手284保留字段uint32一般为0可忽略这个结构和C语言里的结构体非常像用Python解析的时候最直接的方式就是用struct库按格式拆包。你可以理解成整份.day文件就是一张只有一行一行数字的表每一行32个字节解析的过程就是把这32个字节按规则切成8个字段。3.2 几个关键细节乘100、整数日期、量额单位第一次看这个格式很多人会懵为什么价格要乘100存为什么不直接存浮点数这是早期行情协议的设计习惯。用整数存储价格可以避免浮点数在比较和存储过程中的精度问题而且int32的最高价、最低价也能表示到足够精度。比如收盘价9.6元文件里存的是960解析的时候除以100就得到9.6元。这个处理很简单但一旦忘记除100回测结果会非常离谱价格凭空多了两位小数。日期字段同样不是常见的时间戳而是直接存成YYYYMMDD的整数。比如2023年12月1日文件里就是一个整数20231201。转成Python的datetime对象也不复杂先把整数转成字符串再按格式解析就行。成交量和成交额也要特别注意。成交量在.day文件里存的原始值通常需要除以100才是股票软件里显示的手数。但这里有一个坑不同券商版本的通达信量额单位可能存在差异。所以我强烈建议第一次写完解析代码别急着跑全市场先拿一只股票最近20个交易日的解析结果和通达信软件界面上的数字逐项核对一遍确认单位没问题了再批量处理。4. 完整实战两种方式解析通达信.day文件4.1 方式一手写struct解析把原理钉死先来手写一个完整的解析函数。这种方式的好处是能让你彻底理解.day文件的格式出现问题也知道从哪排查。import struct from pathlib import Path from datetime import datetime def parse_day_file(filepath): 手写解析通达信.day文件 返回一个列表每个元素是一个dict包含 date, open, high, low, close, amount, volume data Path(filepath).read_bytes() # 每条记录32字节 record_size 32 # 格式化字符串的含义 # 表示小端序 # I 表示无符号32位整数 # f 表示32位浮点数 # 一共8个字段正好32字节 unpack_format IIIIIfII bars [] for offset in range(0, len(data), record_size): record data[offset:offset record_size] if len(record) record_size: break date_int, open_int, high_int, low_int, close_int, amount, volume, reserved \ struct.unpack(unpack_format, record) date_str str(date_int) # 日期格式是YYYYMMDD转成datetime dt datetime.strptime(date_str, %Y%m%d) bars.append({ date: dt, open: open_int / 100.0, high: high_int / 100.0, low: low_int / 100.0, close: close_int / 100.0, amount: amount, volume: volume / 100.0, }) return bars if __name__ __main__: bars parse_day_file(rC:\new_tdx\vipdoc\sh\lday\sh600000.day) for bar in bars[-5:]: print(bar)这段代码跑出来的结果类似这样{date: datetime.datetime(2023, 12, 1, 0, 0), open: 9.5, high: 9.65, low: 9.45, close: 9.6, amount: 1180000000.0, volume: 1234.56}手写struct的优点是完全不依赖库哪怕换个环境、不装pytdx也能跑。缺点是需要自己处理日期转换、字段命名、单位换算这些细节。如果你只是想快速拿到数据跑策略这种方式稍微有点繁琐。4.2 方式二pytdx.reader一行读取日常首选其实pytdx本身自带了reader模块专门用来解析通达信本地数据文件。用它的TdxDayBarReader一行代码就能读完一个.day文件from pytdx.reader import TdxDayBarReader filepath rC:\new_tdx\vipdoc\sh\lday\sh600000.day # 方式1拿到bar对象列表 bars TdxDayBarReader().get_bars(filepath) # 方式2直接拿到pandas DataFrame df TdxDayBarReader().get_df(filepath) print(df.head())这里get_df返回的DataFrame列名和通达信软件里的字段对应关系如下year month day open close high low vol amount 2023 12 1 9.50 9.60 9.65 9.45 1234.56 1.18e09可以看到pytdx.reader已经帮我们处理了价格除以100、日期拆分成year、month、day这些事用起来非常省心。对大部分日常分析场景来说直接用它就够了。提示pytdx.reader返回的Bar对象里年份、月份、日期是拆开的使用的时候需要自己拼成datetime索引。如果需要把日期作为索引建议用pd.to_datetime拼一下。4.3 两种方式对比和性能选型我整理了一张对比表方便你在实际项目里做选择对比项手写struct解析pytdx.reader解析依赖只需Python标准库需要安装pytdx代码量约30行1-2行可读性格式转换逻辑清晰简洁但掩盖细节性能单文件很快批量一般单文件很快批量效率更高适合场景学习原理、排查格式问题日常分析、策略回测如果你的项目要一次性处理全市场几千个.day文件手写struct循环逐条unpack会稍微吃力这时有两个优化思路。第一个思路是用pytdx.reader它内部做了批量解析优化。第二个思路是用numpy一次把整个文件读成数组再按列处理。numpy的方式我简单展示一下import numpy as np def parse_day_numpy(filepath): # 用np.fromfile一次性读取所有数据 raw np.fromfile(filepath, dtypeu4) # 每8个uint32为一组正好是32字节一条记录 raw raw.reshape(-1, 8) dates raw[:, 0].astype(str) open_prices raw[:, 1] / 100.0 high_prices raw[:, 2] / 100.0 low_prices raw[:, 3] / 100.0 close_prices raw[:, 4] / 100.0 # 注意第5列是float32需要单独读 # 实际生产中用np.memmap会更省内存 return dates, open_prices, high_prices, low_prices, close_prices这种方式在数据量极大的时候优势很明显几千只股票的处理时间能从分钟级压到秒级。不过日常用pytdx.reader已经完全够快了我一般建议先从pytdx.reader入手等真遇到性能瓶颈再上numpy优化。5. 解析过程中最容易踩的坑一次说清5.1 数据对不上先查复权和单位最常见的问题就是解析出来的数据和通达信软件上显示的K线对不上。这时候先别怀疑解析代码大概率是复权状态不一致。.day文件里存的是不复权原始价格。通达信软件默认显示的是前复权价格尤其是除权除息日之后两者价格会差很多。如果你拿.day文件的数据去和软件默认界面比对日期越久差别越大因为前复权会不断把历史价格往下调整。我在实际项目里把本地.day文件作为“不复权底稿”需要前复权或后复权的数据时再结合股票的除权除息数据来计算。这样做的可控性最强。用pytdx在线接口拉日线时同样要确认接口给你的是不复权还是复权后的数据不同版本默认值不一样。单位问题也一样容易踩。价格除以100这个逻辑大家都记得但volume乘不乘100就很容易漏。所以我在4.3节里强调过第一次写完解析务必拿软件界面逐项核对20个交易日左右的数据把价格、成交量、成交额全部对齐以后再批量入库。5.2 文件路径和读取环境的坑第二类问题集中在文件路径上。很多人的通达信安装路径里带空格或者中文直接用原始路径没问题但如果你把路径写死在代码里换一台电脑就要改一次。建议用pathlib来处理路径或者做一个配置文件统一管理数据目录。还有一个小坑通达信如果在运行中某些文件可能会被占用导致读取时出现权限错误。我遇到过几次读取到一半报错的情况后来总结出的经验是先从vipdoc目录把需要的.day文件复制到一个临时目录再从临时目录解析。文件数量不大复制成本极低但能避免很多莫名其妙的问题。另外不是所有股票都有对应的.day文件。新股上市当天可能还没落盘退市股票的数据可能被清理停牌时间特别长的股票文件记录数也会偏少。批量处理时文件不存在或者文件为空都不要直接报错打印一条日志跳过让整个流程跑完。5.3 常见问题速查表现象可能原因解决办法日期解析出来是负数或乱码用了有符号整数或者字节序错误使用struct的I格式保证小端序价格比软件显示多了两位小数忘记除以100开高低收全部除以100成交量比软件显示大了100倍成交量单位没有处理volume除以100再和软件核对读不到文件路径不对或行情目录结构不同检查是否存在vipdoc/sh/lday结构数据只有最近几天通达信软件没有开通全部历史数据在通达信里做一次盘后数据下载数据日期和软件对不上复权状态不一致明确day文件是不复权数据批量处理时中途报错个别文件为空或损坏加异常处理打印日志跳过用get_df方法报错pytdx版本太老执行pip install --upgrade pytdx注意如果你发现解析出来的历史数据起始时间早于预期先检查一下通达信的数据下载设置。有些版本默认只保留最近几年的日线需要在软件里手动执行一次“盘后数据下载”把完整历史补齐。6. 数据解析完不算完增量更新、入库与回测衔接6.1 每天收盘后的增量更新流程.day文件是通达信每天收盘后自动更新的所以你的解析任务最好也做成增量式的每天只需处理新增的那一天数据不用每天全量重跑。我的做法是每天收盘后跑一个定时任务先从vipdoc目录把当天更新的.day文件复制到工作目录然后解析最近两根K线防止昨天因为节假日或停牌漏更新追加到历史数据表里。追加之前先按日期去重避免重复插入。如果你不想依赖本地方言文件的更新也可以用pytdx在线拉当日日线来补充from datetime import datetime from pytdx.hq import TdxHq_API api TdxHq_API() with api.connect(119.147.212.81, 7709): # 拉最近5根日线 bars api.get_security_bars(9, 0, 600000, 0, 5) df api.to_df(bars) today datetime.now().date() today_bar df[df.apply(lambda r: datetime(r[year], r[month], r[day]).date(), axis1) today] print(today_bar)这样即使你忘记了拷贝.day文件在线接口也能帮你把当天的数据补齐。6.2 全市场批量读取与存储建议做全市场策略时你需要把sh和sz两个目录下所有.day文件都读出来。这一步建议用pytdx.reader配合全局遍历from pathlib import Path from pytdx.reader import TdxDayBarReader vipdoc_dir Path(rC:\new_tdx\vipdoc) output_dir Path(rD:\market_data) output_dir.mkdir(exist_okTrue) # 遍历所有市场的lday目录 for lday_dir in vipdoc_dir.glob(*/lday): for day_file in lday_dir.glob(*.day): try: code day_file.stem[2:] # 去掉sh/sz前缀 df TdxDayBarReader().get_df(str(day_file)) if df.empty: continue # 生成日期索引 df[datetime] pd.to_datetime( df[year].astype(str) - df[month].astype(str) - df[day].astype(str) ) df df.set_index(datetime) # 按股票代码保存建议用parquet格式 df.to_parquet(output_dir / f{code}.parquet) except Exception as e: print(f解析 {day_file} 失败: {e})存储格式上我的建议是优先用parquet不要直接用CSV。CSV占用空间大读起来也慢parquet是列式存储几百M的数据也能秒读还能保留数据类型和索引后续做回测会舒服很多。如果你的环境不支持parquet退而求其次可以用HDF5或者SQLite尽量不要裸存CSV。6.3 和回测框架衔接的一点实践经验数据解析完最终目的是喂给回测框架用。无论你用backtrader、vnpy还是自己写的回测引擎有几个点一定处理到位。第一是索引对齐。回测框架通常要求时间索引是datetime类型而且最好按升序排列。从.day文件解析出来的数据本身是按日期递增的但经过合并、更新后可能会乱序入库前做一个sort_index很必要。第二是复权处理。如果你的策略涉及除权除息日附近的信号不复权数据会给出错误信号。建议在入库前就把复权问题想清楚要么统一用前复权要么在策略层面对除权除息做处理最忌讳的是今天用不复权、明天用前复权口径来回变。第三是别把成交量单位弄混。有的框架默认成交量单位是股有的默认是手。你在入库时如果做了除100处理就要确保框架也是这么理解的否则最终盈亏数据会错得很离谱。我之前就因为单位问题跑出来的策略年化收益虚高了大几十个点那会儿还以为是策略厉害后来一查纯粹是数据口径问题。最后说一点我自己的习惯每次解析完一批.day文件我会随机抽两三只股票把最近20天的K线和通达信软件显示的一一核对一遍确认单位、复权、日期都没有偏差再入库。这个习惯帮我挡掉了好几次数据口径翻车的风险。希望大家拿着这份代码也能先把数据对齐这一步做扎实。
返回列表