通达信.day文件解析:Python实现二进制股票数据转CSV全攻略

📅 2026/8/14 18:29:30
通达信.day文件解析:Python实现二进制股票数据转CSV全攻略
1. 项目概述从通达信.day文件到结构化CSV的实战转换在量化交易或者股票数据分析的入门阶段很多朋友都会遇到一个非常具体且实际的问题从哪里获取高质量、格式规整的历史行情数据如果你和我一样最初的研究起点是通达信这类主流股票软件那么你大概率会接触到一种名为.day的二进制数据文件。这些文件小巧、高效存储着A股市场多年的日线数据是本地化分析的宝贵矿藏。然而它们的二进制格式就像一本用密码写成的书不经过“翻译”Python的Pandas、NumPy等数据分析库根本无法直接读取和利用。本期要解决的正是这个“翻译”问题——如何将通达信的.day文件准确、高效地转换为通用的CSV格式从而打通本地数据与Python分析生态之间的壁垒。这个过程远不止是简单的格式转换。它涉及到对金融数据格式的逆向解析、二进制数据处理的精确性、以及批量处理大量文件时的工程化思维。一个稳定可靠的转换工具是你构建个人量化分析数据库、进行策略回测、乃至开发自动化交易系统的第一步也是最基础、最关键的一步。本文将基于我个人的实践详细拆解从理解.day文件结构到编写Python转换脚本再到优化处理流程的完整心路历程并提供可直接复现的代码和避坑指南。2. 核心需求与文件格式深度解析2.1 为什么必须转换二进制.day文件的局限与CSV的优势通达信的.day文件设计初衷是为了软件自身的高速读取和渲染它采用紧凑的二进制格式去除了所有“冗余”的文本信息。这带来了极高的存储和读取效率但也带来了几个对数据分析者不友好的问题不可直接阅读与编辑用文本编辑器打开是一堆乱码无法直观查看或进行简单修改。缺乏通用性除了通达信及其衍生工具几乎没有其他第三方库或平台原生支持这种格式。数据结构不透明字段的含义、顺序、数据类型如复权因子如何处理都隐藏在二进制流中需要精确的格式定义才能解析。相比之下CSVComma-Separated Values格式几乎是数据交换的“世界语”。它的优势显而易见人类可读用Excel、文本编辑器都能直接打开查看和编辑。生态兼容Pandas的read_csv、数据库的导入工具、乃至任何编程语言都有成熟的CSV支持库。结构清晰列名、数据类型一目了然便于后续的清洗、计算和可视化。因此转换的核心需求就是无损地将.day文件中包含的日期、开盘价、最高价、最低价、收盘价、成交量、成交额等核心信息按照明确的规则提取出来并组织成结构化的表格数据存储为CSV文件。2.2 通达信.day文件格式详解这是整个转换过程的基石理解错误会导致解析出的数据完全错乱。根据公开资料和个人验证通达信日线数据.day文件的典型结构如下每个.day文件对应一只股票或指数文件名通常是股票代码如000001.day对应平安银行。文件内部由一条条记录顺序排列每条记录固定为32字节没有文件头或索引。这32字节按顺序存储了以下信息字节偏移 (起始为0)字段长度 (字节)数据类型 (C语言类比)字段含义说明与处理方式0 - 34int日期 (Date)存储格式为YYYYMMDD的整数。例如20231027。4 - 74int开盘价 (Open)实际价格乘以1000或100见下方注意事项。8 - 114int最高价 (High)实际价格乘以1000或100。12 - 154int最低价 (Low)实际价格乘以1000或100。16 - 194int收盘价 (Close)实际价格乘以1000或100。20 - 234float成交金额 (Amount)单位通常是“元”但以“千元”或“万元”为存储单位的情况也存在需根据数据验证。24 - 274int成交量 (Volume)单位是“股”。对于指数可能是“手”或放大倍数。28 - 314int保留字段通常为0部分版本可能用于存储换手率或其他信息但普遍解析时不使用。注意事项价格精度问题——这是最大的坑网络上关于价格字段开盘、最高、最低、收盘的乘数因子主要有两种说法100和1000。这很可能是因为通达信在不同时期、或对不同类型数据股票/基金/指数采用了不同的存储精度。最可靠的方法是进行验证找一只你知道某天确切价格的股票解析出来对比。例如茅台某日收盘价若为1700.50元解析出的整数值如果是170050则除数为100如果是1700500则除数为1000。我的经验是A股股票的.day文件普遍使用1000作为乘数即价格精确到0.001元。但在处理时务必将此作为可配置参数。2.3 转换流程的整体设计思路一个健壮的转换工具不应是“一次性脚本”而应具备可配置、可批量、可容错的能力。我的设计思路如下输入/输出配置化允许用户指定源.day文件目录或单个文件、输出CSV目录、价格除数和金额单位。核心解析引擎一个函数负责读取32字节块按照上述格式解析并应用单位转换。批量处理与进度反馈遍历目录下所有.day文件逐个转换并提供进度条或日志便于监控。数据增强与格式化将整数日期转换为YYYY-MM-DD格式可以添加股票代码列从文件名提取使CSV更完整。异常处理与日志记录对损坏的文件、格式不符的文件进行跳过并记录保证批量处理不因单个错误而中断。3. 工具选型与环境准备3.1 Python库的选择我们只需要Python标准库和Pandas无需复杂依赖。structPython标准库模块用于解析二进制数据流。它是本次转换的“解码器”。pandas数据处理的核心用于将解析后的列表构建成DataFrame并保存为CSV。pathlib(或os)用于文件和路径操作pathlib是现代Python更推荐的方式。tqdm(可选但推荐)为循环添加进度条在批量处理数百个文件时体验提升巨大。安装命令非常简单pip install pandas tqdm3.2 项目结构规划一个清晰的项目结构有助于管理代码和数据。tdx_day_to_csv/ ├── src/ │ ├── converter.py # 核心转换函数 │ └── batch_processor.py # 批量处理与命令行接口 ├── data/ │ ├── day_files/ # 存放从通达信导出的.day文件 │ └── csv_output/ # 转换后的CSV文件输出目录 ├── config.yaml (或 .py) # 配置文件存放除数、路径等参数 └── README.md # 项目说明对于初学者或简单使用将所有代码放在一个脚本里也完全可行。但了解这种结构有助于你未来构建更复杂的项目。4. 核心代码实现与逐步拆解4.1 单文件解析函数的实现这是最核心的部分我们定义一个函数parse_day_file它接受文件路径和配置参数返回一个Pandas DataFrame。import struct import pandas as pd from pathlib import Path from typing import Optional, List def parse_day_file(file_path: Path, price_divisor: int 1000, amount_divisor: float 10000.0) - Optional[pd.DataFrame]: 解析单个通达信.day文件。 参数: file_path: .day文件的Path对象。 price_divisor: 价格字段的除数通常为100或1000。 amount_divisor: 成交额字段的除数通常为10000代表万元或1000代表千元。 返回: 包含日期、OHLC、成交量、成交额的DataFrame解析失败则返回None。 data [] record_format iiiiifii # struct格式字符串4个int1个float3个int record_size struct.calcsize(record_format) # 应为32字节 try: with open(file_path, rb) as f: content f.read() except IOError as e: print(f无法读取文件 {file_path}: {e}) return None # 检查文件大小是否是记录大小的整数倍 if len(content) % record_size ! 0: print(f警告文件 {file_path} 大小异常可能已损坏。) # 可以选择继续解析但这里我们保守处理返回None或尝试解析 # 这里我们尝试解析能整除的部分 pass # 计算记录条数 num_records len(content) // record_size for i in range(num_records): start i * record_size end start record_size record_bytes content[start:end] try: # 解包二进制数据 date_int, open_price, high, low, close, amount, volume, _ struct.unpack(record_format, record_bytes) except struct.error as e: print(f解析文件 {file_path} 第 {i1} 条记录时出错: {e}) continue # 跳过这条记录继续解析下一条 # 转换日期格式 date_str str(date_int) if len(date_str) 8: formatted_date f{date_str[:4]}-{date_str[4:6]}-{date_str[6:8]} else: formatted_date str(date_int) # 备用方案 # 转换价格和成交额 # 注意先转换为浮点数进行除法以保留小数 open_price open_price / price_divisor high high / price_divisor low low / price_divisor close close / price_divisor amount amount / amount_divisor # 成交额通常单位为万元 # 组装数据 data.append([ formatted_date, open_price, high, low, close, volume, amount # 注意顺序成交量(股)成交额(万元) ]) if not data: print(f文件 {file_path} 未解析出有效数据。) return None # 创建DataFrame df pd.DataFrame( data, columns[date, open, high, low, close, volume, amount] ) # 按日期升序排列.day文件通常是倒序存储即最新数据在前 df[date] pd.to_datetime(df[date]) df df.sort_values(date).reset_index(dropTrue) return df代码关键点解析struct.unpack(iiiiifii, record_bytes)这是解码二进制数据的核心。格式字符串iiiiifii对应了之前表格中的8个字段4个int (i)1个float (f)再3个int (i)。错误处理使用try-except包裹文件读取和解包操作防止因个别文件损坏导致整个程序崩溃。单位转换price_divisor和amount_divisor是关键参数。这里假设价格除以1000成交额除以10000即存储单位是“万元”。你必须根据实际数据验证并调整这两个参数日期格式化将YYYYMMDD整数转换为YYYY-MM-DD字符串便于Pandas识别。排序.day文件通常最新日期在最前面我们按日期升序排列更符合分析习惯。4.2 批量转换与主程序逻辑接下来我们编写批量处理的函数并添加进度显示。from tqdm import tqdm import sys def batch_convert_day_to_csv(input_dir: Path, output_dir: Path, price_divisor: int 1000, amount_divisor: float 10000.0, suffix: str .day) - None: 批量转换指定目录下的所有.day文件为CSV。 参数: input_dir: 包含.day文件的目录。 output_dir: 输出CSV文件的目录。 price_divisor: 价格除数。 amount_divisor: 成交额除数。 suffix: 要处理的文件后缀默认为.day。 # 创建输出目录 output_dir.mkdir(parentsTrue, exist_okTrue) # 获取所有.day文件 day_files list(input_dir.glob(f*{suffix})) if not day_files: print(f在目录 {input_dir} 中未找到{suffix}文件。) return print(f找到 {len(day_files)} 个{suffix}文件开始转换...) success_count 0 fail_count 0 failed_files [] # 使用tqdm添加进度条 for day_file in tqdm(day_files, desc转换进度, unitfile): # 解析单个文件 df parse_day_file(day_file, price_divisor, amount_divisor) if df is not None and not df.empty: # 生成输出文件名股票代码.csv stock_code day_file.stem # 去除后缀的文件名即股票代码 output_path output_dir / f{stock_code}.csv # 保存为CSV索引不写入文件 df.to_csv(output_path, indexFalse, encodingutf-8-sig) # utf-8-sig兼容Excel中文 success_count 1 else: fail_count 1 failed_files.append(day_file.name) # 打印总结报告 print(\n *50) print(转换完成) print(f成功: {success_count} 个文件) print(f失败: {fail_count} 个文件) if failed_files: print(失败的文件列表:) for f in failed_files: print(f - {f}) print(*50) if __name__ __main__: # 示例使用Path对象指定路径 input_directory Path(rD:\tdx_data\vipdoc\sh\lday) # 上海股票日线目录示例 output_directory Path(r./csv_output) # 调用批量转换函数 # 注意务必根据你的数据验证price_divisor和amount_divisor batch_convert_day_to_csv( input_dirinput_directory, output_diroutput_directory, price_divisor1000, # 关键参数可能需要调整为100 amount_divisor10000.0 # 关键参数可能需要调整为1000或1 )4.3 数据验证与参数校准在正式批量转换前强烈建议进行数据验证。编写一个简单的测试脚本def validate_sample(file_path: Path, known_date: str, known_close: float): 用已知日期和收盘价验证解析是否正确 df parse_day_file(file_path, price_divisor1000, amount_divisor10000.0) if df is not None: sample df[df[date] known_date] if not sample.empty: parsed_close sample.iloc[0][close] if abs(parsed_close - known_close) 0.01: # 允许微小浮点误差 print(f验证通过解析收盘价: {parsed_close}, 已知收盘价: {known_close}) return True else: print(f验证失败解析收盘价: {parsed_close}, 已知收盘价: {known_close}) print(尝试调整 price_divisor 为 100...) df2 parse_day_file(file_path, price_divisor100, amount_divisor10000.0) sample2 df2[df2[date] known_date] if not sample2.empty: parsed_close2 sample2.iloc[0][close] if abs(parsed_close2 - known_close) 0.01: print(f使用 price_divisor100 验证通过) return True else: print(f未找到日期 {known_date} 的数据。) return False # 示例验证平安银行(000001)某一天的数据 test_file Path(r000001.day) if validate_sample(test_file, 2023-10-27, 10.50): # 请替换为真实的日期和价格 print(参数设置正确可以开始批量转换。) else: print(请检查数据源和解析参数。)5. 高级处理与功能扩展5.1 处理复权因子难点与方案原始的.day文件存储的是前复权或不复权的价格。如果你需要后复权数据或者需要自己计算复权因子这是一个更高级的话题。通常你需要从通达信或其他数据源获取权息数据包含分红、送股、配股等信息然后根据算法对价格和成交量进行复权计算。简易方案如果.day文件已是前复权数据很多从通达信导出的.day文件默认已经是处理好的前复权数据。这种情况下你转换出来的CSV直接就是前复权价格可以直接用于大多数技术分析如均线、MACD计算因为价格序列是连续的。进阶方案自行复权获取权息数据通常是一个类似gbbq的文本或二进制文件。解析权息数据得到每次除权除息的日期和比例。编写复权算法从某个基准点通常是最近一次除权开始向前或向后逐日推导复权价格。将复权因子应用到转换出的基础CSV数据上。由于复权计算相对复杂且依赖于额外的权息数据源在初期转换时可以先确认你的.day文件数据状态。如果满足需求可暂不处理此部分。5.2 添加股票代码与数据合并在批量转换后你得到了成百上千个以股票代码命名的CSV文件。有时我们需要将所有数据合并到一个大文件中进行分析。def merge_all_csv(csv_dir: Path, output_file: Path): 合并目录下所有CSV文件并添加股票代码列 all_dfs [] for csv_file in csv_dir.glob(*.csv): stock_code csv_file.stem try: df pd.read_csv(csv_file) df[code] stock_code # 添加股票代码列 all_dfs.append(df) except Exception as e: print(f读取文件 {csv_file} 时出错: {e}) if all_dfs: merged_df pd.concat(all_dfs, ignore_indexTrue) # 可以按代码和日期排序 merged_df.sort_values([code, date], inplaceTrue) merged_df.to_csv(output_file, indexFalse) print(f已合并 {len(all_dfs)} 个文件到 {output_file}) else: print(没有找到可合并的CSV文件。)5.3 使用配置文件管理参数将关键参数外置到配置文件如config.py或config.yaml提高灵活性。config.py示例# config.py INPUT_PATH rD:\tdx_data\vipdoc OUTPUT_PATH r./csv_output PRICE_DIVISOR 1000 # 关键根据验证结果修改 AMOUNT_DIVISOR 10000.0 # 关键根据验证结果修改主程序中使用from config import INPUT_PATH, OUTPUT_PATH, PRICE_DIVISOR, AMOUNT_DIVISOR batch_convert_day_to_csv(Path(INPUT_PATH), Path(OUTPUT_PATH), PRICE_DIVISOR, AMOUNT_DIVISOR)6. 常见问题、排查技巧与实操心得6.1 问题排查清单问题现象可能原因解决方案转换出的价格巨大如几万price_divisor设置错误可能应为1000但设成了1。使用validate_sample函数验证并调整price_divisor参数。转换出的价格是小数但明显不对如股价0.0xprice_divisor设置错误可能应为100但设成了1000。同上验证并调整参数。成交额数值异常大或小amount_divisor设置错误。通达信可能以“元”、“千元”或“万元”存储。找一天已知成交额的数据进行验证。常见值为10000.0万元或1000.0千元。解析时struct.error报错1. 文件损坏。2. 文件格式不是标准的32字节/记录。3. 可能是分钟线等其它类型数据。1. 重新获取数据。2. 检查文件大小是否为32的整数倍。3. 确认文件来源日线数据后缀应为.day。转换后CSV用Excel打开乱码编码问题。Excel默认可能不识别UTF-8无BOM。在to_csv()方法中指定encodingutf-8-sig。日期列无法被Pandas识别为时间序列CSV中的日期格式可能不是YYYY-MM-DD。确保parse_day_file函数中日期格式转换正确或用pd.to_datetime(df[date])强制转换。批量转换时内存不足同时将所有数据读入内存合并。批量处理时应逐文件解析并立即保存为单个CSV而不是先积累所有DataFrame。6.2 实操心得与避坑指南数据源是第一位的确保你的.day文件来源可靠、完整。不同版本的通达信或数据导出工具生成的文件格式可能有细微差别。拿到数据后第一步永远是验证用少数几只股票、几个已知的日期和价格去测试你的解析逻辑。参数没有银弹price_divisor和amount_divisor没有绝对正确的值。它取决于数据生成的方式。对于从网上获取的第三方整理的数据尤其要小心。我遇到过同一个数据包内部分股票除数为1000部分为100的混乱情况虽然罕见。最稳妥的方式是为每只股票抽样验证。文件路径处理使用pathlib.Path代替字符串拼接来处理路径它能自动处理不同操作系统的路径分隔符问题代码更清晰、安全。内存与效率对于全市场几千只股票的历史数据批量转换可能产生几十GB的CSV文件。在合并所有数据时要谨慎考虑是否真的需要单个巨型文件。通常按股票代码分文件存储使用时按需加载是更灵活的做法。版本控制与日志将转换脚本和配置文件纳入Git管理。在批量转换函数中加入详细的日志记录可以使用Python的logging模块记录成功、失败的文件名和可能的原因便于后续排查。超越CSVCSV是很好的起点但对于超大规模数据查询效率不高。转换完成后可以考虑将数据导入到SQLite轻量级或DuckDB高性能分析型数据库中甚至转换为Parquet等列式存储格式能极大提升后续分析的性能。从通达信原始的.day文件到规整的CSV这一步看似简单却涵盖了二进制处理、数据格式理解、参数校准、批量工程化等多个环节。走通这个过程不仅让你获得了可用的数据更让你深刻理解了金融数据处理的底层细节。这份可控的、本地化的数据将成为你进行任何量化探索的坚实起点。当你第一次用自己的脚本成功转换出数据并用Matplotlib画出一只股票的K线图时那种成就感是直接使用现成数据API无法比拟的。