Python实战:解析通达信day文件并转换为CSV,助力期货历史回测
1. 为什么需要解析通达信day文件做期货量化交易的朋友都知道历史数据是回测的基础。通达信的day文件包含了丰富的期货历史交易数据但它的二进制格式让很多新手望而却步。我自己刚开始做量化时就曾被这个数据格式困扰了很久。day文件包含了每个交易日的开盘价、最高价、最低价、收盘价、成交量等重要数据。这些数据对于构建交易策略至关重要。但直接用Python读取这些二进制文件对新手来说确实不太友好。这就是为什么我们需要把它转换成更易读的CSV格式。CSV格式有几个明显优势可以用Excel直接打开查看可以用pandas轻松处理可以和其他数据源无缝对接。我见过不少交易员还在手动记录数据其实只要掌握这个转换技巧效率能提升好几倍。2. 理解通达信day文件结构要正确解析day文件首先得了解它的二进制结构。经过多次测试和验证我发现每个交易日的数据记录固定占用32字节。具体结构是这样的0-3字节日期4字节整型格式YYYYMMDD4-7字节开盘价4字节浮点数8-11字节最高价4字节浮点数12-15字节最低价4字节浮点数16-19字节收盘价4字节浮点数20-23字节持仓量4字节整型24-27字节成交量4字节整型28-31字节结算价4字节浮点数这里有个坑要注意通达信的日期存储方式比较特殊。比如20230115会存储为整数20230115而不是时间戳。我在第一次尝试时就被这个细节坑过解析出来的日期全是错的。3. 准备Python解析环境在开始写代码前我们需要准备好Python环境。我推荐使用Anaconda它自带了数据分析常用的库。需要安装的依赖其实很少主要是以下几个import os import struct import datetimestruct模块是关键它负责处理二进制数据的解包。datetime用于日期格式化。如果你已经安装了Python标准库这些模块应该都已经自带了。建议新建一个专门的文件夹来存放day文件和转换后的CSV文件。我通常这样组织目录结构/project /raw_data # 存放原始day文件 /csv_data # 存放转换后的CSV converter.py # 转换脚本4. 完整代码实现与解析下面是我优化过的完整转换代码比原始版本增加了错误处理和日志输出def convert_day_to_csv(input_path, output_dir): 将通达信day文件转换为CSV格式 :param input_path: 输入的day文件路径 :param output_dir: 输出的CSV文件目录 try: with open(input_path, rb) as f: filename os.path.basename(input_path).split(.)[0] output_path os.path.join(output_dir, f{filename}.csv) with open(output_path, w, encodingutf-8) as csv_file: # 写入CSV表头 csv_file.write(Date,Open,High,Low,Close,OpenInterest,Volume,Settlement\n) while True: # 读取32字节的数据块 data f.read(32) if not data: break # 解析各个字段 date struct.unpack(i, data[0:4])[0] open_price struct.unpack(f, data[4:8])[0] high struct.unpack(f, data[8:12])[0] low struct.unpack(f, data[12:16])[0] close struct.unpack(f, data[16:20])[0] open_interest struct.unpack(i, data[20:24])[0] volume struct.unpack(i, data[24:28])[0] settlement struct.unpack(f, data[28:32])[0] # 格式化日期 date_str datetime.datetime.strptime(str(date), %Y%m%d).strftime(%Y-%m-%d) # 写入CSV行 csv_line f{date_str},{open_price:.2f},{high:.2f},{low:.2f},{close:.2f},{open_interest},{volume},{settlement:.2f}\n csv_file.write(csv_line) except Exception as e: print(f转换失败: {input_path}, 错误: {str(e)})这个版本有几个改进点使用了更规范的函数参数命名增加了异常处理输出价格保留了两位小数日期格式化更准确添加了详细的注释5. 批量转换与自动化处理实际使用时我们通常需要批量转换多个day文件。这里分享一个我常用的批量处理脚本def batch_convert(input_dir, output_dir): 批量转换目录下的所有day文件 :param input_dir: 输入目录 :param output_dir: 输出目录 if not os.path.exists(output_dir): os.makedirs(output_dir) count 0 for filename in os.listdir(input_dir): if filename.endswith(.day): input_path os.path.join(input_dir, filename) try: convert_day_to_csv(input_path, output_dir) count 1 print(f成功转换: {filename}) except Exception as e: print(f转换失败: {filename}, 错误: {str(e)}) print(f转换完成共处理{count}个文件)使用示例if __name__ __main__: # 配置路径 raw_data_dir C:/tdx/vipdoc/ds/lday # 通达信day文件目录 csv_output_dir ./csv_data # CSV输出目录 # 执行批量转换 batch_convert(raw_data_dir, csv_output_dir)我建议把这个脚本设置为定时任务每天收盘后自动更新数据。这样就能始终保持数据是最新的回测时直接用最新数据即可。6. 数据验证与常见问题转换完成后一定要验证数据的准确性。我总结了几种常见的验证方法基础检查确保日期是连续的检查最高价是否≥最低价确认收盘价在最高价和最低价之间对比验证随机抽取几天数据与通达信软件显示的数据对比检查成交量是否合理统计分析计算基本统计量均值、标准差检查价格变动范围是否合理常见问题及解决方案问题1转换后的CSV文件乱码解决方案在打开CSV文件时指定encodingutf-8问题2日期格式错误解决方案检查struct.unpack的参数是否正确确保使用i而不是l问题3价格显示为科学计数法解决方案在写入CSV时使用f{price:.2f}格式化7. 在回测中的应用技巧有了CSV格式的历史数据我们就可以用pandas轻松加载和分析import pandas as pd def load_csv_data(filepath): 加载CSV数据到DataFrame df pd.read_csv(filepath) df[Date] pd.to_datetime(df[Date]) df.set_index(Date, inplaceTrue) return df回测时的几个实用技巧数据清洗# 处理缺失值 df.fillna(methodffill, inplaceTrue) # 去除异常值 df df[(df[High] df[Low]) (df[Volume] 0)]计算技术指标# 计算20日均线 df[MA20] df[Close].rolling(20).mean() # 计算MACD exp12 df[Close].ewm(span12, adjustFalse).mean() exp26 df[Close].ewm(span26, adjustFalse).mean() df[MACD] exp12 - exp26 df[Signal] df[MACD].ewm(span9, adjustFalse).mean()回测框架集成 我通常会把转换好的数据导入Backtrader或Zipline等回测框架。以Backtrader为例from backtrader.feeds import GenericCSVData class TdxCSVData(GenericCSVData): params ( (dtformat, %Y-%m-%d), (datetime, 0), (open, 1), (high, 2), (low, 3), (close, 4), (volume, 6), (openinterest, 5), )8. 性能优化建议当处理大量历史数据时性能就变得很重要。我总结了几点优化经验使用多进程from multiprocessing import Pool def parallel_convert(file_list, output_dir): with Pool() as pool: pool.starmap(convert_day_to_csv, [(f, output_dir) for f in file_list])内存优化逐行处理而不是一次性读取整个文件使用生成器减少内存占用缓存机制对已转换的文件做标记避免重复转换使用hash检查文件是否修改过使用更高效的数据结构import numpy as np # 使用numpy数组存储数据 data np.zeros((record_count, 8), dtypenp.float32)对于超大规模数据我建议考虑使用Dask或者PySpark这样的分布式计算框架。不过对于一般的期货回测需求上面的优化方法已经足够了。