分笔文件解析
Creato il: 22 gennaio 2025
Creato il: 22 gennaio 2025
我已下载了这个分笔文件、I:\DZH\国信\datadir\SZ\0\300420\20250122.dat
你将这个文件按链接的代码来读取https://zhuanlan.zhihu.com/p/534110919
它应是 144个字节为一行
timetag:日期时间
lastPrice:最新价
open:开盘价
high:最高价
low:最低价
lastClose:昨收价
amount:成交额
volume:成交量(精确到手)
pvolume:成交量(精确到股)
stockStatus:股票状态(作废)
openInt:若是股票则含义为股票状态,非股票则是持仓量
settlementPrice:结算价
lastSettlementPrice:最新结算价
askPrice:列表,卖价五档
bidPrice:列表,买价五档
askVol:列表,卖量五档
bidVol:列表,买量五档
下面给出一个示例的 Python 代码,演示如何按照常见“大智慧分笔(Tick)格式”去解析每条 144 字节的记录,并逐一读取其各个字段。该结构与您提供的字段列表(timetag、lastPrice、open、high、low、lastClose、amount、volume、pvolume、stockStatus、openInt、settlementPrice、lastSettlementPrice、askPrice[5]、bidPrice[5]、askVol[5]、bidVol[5])相对应。
注意:有些大智慧/券商系软件的分笔文件在实际存储中,可能因为对齐(Alignment)或历史版本差异,会存在 136 字节或 144 字节的情况;或是多了/少了某些保留字段,导致名义上 144 字节、但解析时需要考虑 8 字节对齐填充。如果直接用下列格式字符串无法一次性正确解包,往往就是对齐或历史版本差异所致。以下示例基于最常见的 144 字节“小端序”写法。
根据您给出的字段名称以及常见的大智慧分笔格式,可做如下推断(小端序,C/C++/C# 中通常也是这样):
uint32
(4 字节)float
(4 字节)float
(4 字节)float
(4 字节)float
(4 字节)float
(4 字节)double
(8 字节) —— 成交额,常用 8 字节浮点int32
(4 字节)int32
(4 字节)int32
(4 字节) —— 您提到可能“作废”,但通常文件里依旧有这个占位int32
(4 字节)float
(4 字节)float
(4 字节)float[5]
(5×4 = 20 字节)float[5]
(5×4 = 20 字节)int32[5]
(5×4 = 20 字节)int32[5]
(5×4 = 20 字节)将它们串联起来,常见写法(不考虑额外对齐)应总计 136 字节:
但是许多情况下,这个结构在实际二进制文件中会补齐到 144 字节,这 8 字节一般是因为编译器对 double
(8 字节) 进行对齐、或者结构体尾部对齐的填充而来(尤其在 C#/C++ 的默认对齐下,经常会“看起来”总长 144 字节)。若文件确实是 144 字节固定行长,则可以基本确认每行就是这个结构,只是包含 8 字节填充(或者包含某个保留字段)。
下述 Python 代码先按最常见的解析方式(假定无额外填充)写出格式串:
<I f f f f f d i i i i f f 5f 5f 5i 5i
对应 136 字节。如若您确认文件的确是每条 144 字节,而此结构解不出来(比如会报错或读到多余字节),则可以:
x
(或 x8
),表示跳过额外 8 字节(填充/保留字段)。4字节 + 4字节
、或 8字节
)。TimeTag
并非 4 字节,而是 8 字节(有些版本是 long),那就要把 <I
改成 <Q
,并相应调整下后续字段。总之,若遇到对不上号的问题,可以做一些尝试或参考更具体的文档/示例。
下面是一段较为“模板式”的 Python 代码,演示如何循环读取该二进制文件(每次 144 字节),并用 struct
按您提供的字段顺序进行解包、输出。请先尝试用最常见的 <I f f f f f d i i i i f f 5f 5f 5i 5i
,如果发现读取后还有剩余 8 字节(或读出来的数据有错位),再做相应调整。
pythonimport struct # 每条记录大小(字节)。您确认是 144,则可试着先写 144 record_size = 144 # 打开分笔数据文件(二进制方式) with open(r"I:\DZH\国信\datadir\SZ\0\300420\20250122.dat", "rb") as f: while True: data = f.read(record_size) if not data: break # EOF # -------------------------------------------------------------------- # 1) 先尝试最常见的 136 字节布局(无额外 padding): # fmt = '<I f f f f f d i i i i f f 5f 5f 5i 5i' # # 2) 若文件行确实是 144 字节且前面解包成功但发现多 8 字节, # 可以在末尾再跳 8 字节: fmt = '<I f f f f f d i i i i f f 5f 5f 5i 5i 8x' # # 3) 如果 timetag 是 8 字节(例如 long long),可尝试: # fmt = '<Q f f f f f d i i i i f f 5f 5f 5i 5i' # 并视情况是否还要填补 x 字节。 # # 这里先用(2)的方式,假设末尾有 8 字节保留字段(x8 跳过): # -------------------------------------------------------------------- fmt = '<I f f f f f d i i i i f f 5f 5f 5i 5i 8x' # 注意:上面这个格式串加了 “8x”,总计解包 + 跳过 = 144 字节 unpack_size = struct.calcsize(fmt) if len(data) < unpack_size: # 理论上不应该发生,除非文件损坏或最后不完整 break fields = struct.unpack(fmt, data) # 逐个字段对应 # fields[0] = timetag timetag = fields[0] lastPrice = fields[1] openPrice = fields[2] highPrice = fields[3] lowPrice = fields[4] lastClose = fields[5] amount = fields[6] volume = fields[7] pvolume = fields[8] stockStatus = fields[9] openInt = fields[10] settlementPrice = fields[11] lastSettlementPrice = fields[12] # askPrice是 5 个 float askPrice = fields[13:18] # bidPrice是 紧接的 5 个 float bidPrice = fields[18:23] # askVol是 5 个 int askVol = fields[23:28] # bidVol是 5 个 int bidVol = fields[28:33] # -------------------------------------------------------------------- # 根据需要进行处理、打印等: # -------------------------------------------------------------------- print(f"TimeTag={timetag}, Last={lastPrice}, Open={openPrice}, " f"High={highPrice}, Low={lowPrice}, LastClose={lastClose}, " f"Amount={amount}, Volume={volume}, pVolume={pvolume}, " f"Status={stockStatus}, OpenInt={openInt}, " f"Settle={settlementPrice}, PreSettle={lastSettlementPrice}, " f"AskPrice={askPrice}, BidPrice={bidPrice}, " f"AskVol={askVol}, BidVol={bidVol}")
上面这段代码做了以下假设:
8x
跳过。这样可一次性用 struct.unpack
完成解包并自动跨过那 8 字节。如果您发现数据对不上号(例如时间戳怪异、价格对不上),就需要根据实际情况移除或增加对齐跳过字段(x
)、或者将 timetag
的 4 字节改成 8 字节(<Q
)再看是否正确。一些历史版本或者不同软件导出的 .dat
文件,也可能在中间插入了其他保留字段。
timetag
的大小
有的版本里 timetag
占 4 字节 (UInt32),只是里面打包了 “HHmmss” 或者相对于某天开盘的毫秒数/秒数;也有的把日期与时间分开存两个 4 字节;也有的干脆就用 8 字节(long long)。要先确认清楚。
对齐填充
有的编译器/语言默认会在包含 double
的结构体中做对齐;再加上一些尾部 padding,就会让整个结构看似 144 字节,而解析只需要 136。若您强制 Pack=1,可能就变成 136。
stockStatus
字段用途
您提到它是“作废”,但是文件里往往还是会保留 4 字节位置,解析出来只是数值没什么实际意义。
askPrice
/ askVol
等 5 档深度
有些历史版本或者期货品种,可能不只 5 档(也可能是 3 档、10 档),或索性没有档位信息,那样在字节上就会有差异。
结论:
8x
的格式串去“对”数据;如果解析出来的值有明显错位,就需要再在结构中间或开头做相应调整(例如把 timetag
改成 8 字节 <Q>
,或者把 volume/pvolume 改成 8 字节,等等)。这样就基本满足了“按照链接所述代码”去解析大智慧分笔文件的需求。祝您调试顺利!