资讯动态

通达信gbbq文件解密:Python解析除权除息数据到CSV

发布时间:2026/9/29 9:30:36 来源:尧图企业网站定制
你在通达信的安装目录里翻过 T0002 这个文件夹吗里面有个叫 gbbq 的文件没有扩展名就几 MB用记事本打开全是乱码。很多人直接绕开它但它其实装着 A 股每只股票历年来的送股、转增、配股、分红记录是行情软件里除权除息计算和复权处理的地基。这篇文章就从解密 gbbq 文件入手把这套本地数据还原成干净的 CSV顺便把整个逆向分析的思路完整记录下来。如果你是做量化数据落地、想把通达信的历史股本变迁数据搬进自己的系统或者只是好奇这个文件到底存了什么这篇应该能帮你省下不少折腾时间。1. 为什么要啃 gbbq 这块硬骨头1.1 它是股本变迁档案不是行情文件gbbq 这三个字母按大家的习惯理解就是股本变迁的拼音首字母。它记录的是每只股票每一次除权除息事件每股送多少、转增多少、配股比例多少、配股价多少、每股现金分红多少。这些事件只发生在特定时间点不产生连续K线所以日线文件里找不着通达信单独用一个文件来存。我第一次打开这个文件的时候很困惑以为是加密的行情库后来对照自己熟悉的一只股票和复权价格才反应过来这是除权事件的档案。你用记事本看它全是乱码不奇怪紧凑的二进制结构整型、浮点、ASCII 混排还做了字节混淆。它不是给人类阅读的是给通达信自己读的。1.2 本地数据研究绕不开的两个场景搞本地数据仓库的人迟早会碰到它。第一个场景是复权计算。任何做历史回测的人都知道不复权的价格会带着巨大的除权缺口均线、动量、突破策略全都会被假信号干扰。而前复权、后复权不是行情软件凭空生成的是基于除权除息记录动态计算的。没有 gbbq 里的记录历史价格就只是一条锯齿状的断层线。第二个场景是本地备份和迁移。很多人想从通达信切到自建系统或者别的软件K 线文件.day很好解析gbbq 反而是最容易被忽略又最要命的一块。忽略它的结果就是换完平台历史行情的复权全是错的回测结果直接没法看。网上关于通达信的各种指标源码、公式破解讨论多得是但真正把本地数据文件格式讲清楚的反而少。这也是我写这篇的原因指标公式是锦上添花基础数据才是地基。2. 打开文件先看什么加密痕迹与结构线索2.1 十六进制视图下的第一印象分析一个未知文件第一步永远是用十六进制编辑器打开。我用的是 HxD免费、轻量够用。打开后能看到gbbq 文件头部有十几个字节之后全是看似没有规律的字节。这里有一个容易被忽略的分析技巧要区分乱和被处理过的乱。正常的紧凑二进制数据会有明显规律比如大量 0x00 填充字段、连续的 ASCII 字符、周期性重复的数值。而 gbbq 给人的感觉是所有字节的取值都像被均匀地推到了高位区间低频字符和高频字符全都看不出来了。看到这种分布第一反应就应该是文件内容被做了逐字节异或处理。2.2 为什么软件会选异或这种弱加密有人会问既然是加密为什么不用 AES 这类强加密答案是没必要。通达信这类行情软件的本地文件追求的是读得快、占得少它做的不是安全加密而是格式混淆。异或运算的强度确实弱但它对 CPU 的开销极小解密也简单适合每次启动都要读取几万条记录的场景。用一个例子说明异或的效果。假设明文字节是 0x30这是 ASCII 字符 0密钥是 0xAA那么密文就是 0x30 XOR 0xAA 0x9A。如果明文中大量字节是 0x00二进制文件里很常见的填充值异或 0xAA 之后密文里就会出现大量 0xAA。换句话说一个文件如果整体看起来像被均匀打散但又有某个字节出现频率异常高那么这个高频字节很可能就是 0x00 与密钥异或后的结果密钥就藏在里面。2.3 动手前先准备好这些工具和素材其实不多十六进制编辑器HxD 或 010 EditorPython 3 环境确保能跑标准库脚本一份从自己通达信 T0002 目录复制出来的 gbbq 文件先做备份一只有历史除权记录、且你很熟悉的股票比如平安银行000001这里多说一句整个分析过程只针对你自己电脑上的本地文件属于个人数据研究不要拿去做违反软件许可协议的商业分发。3. 密钥定位和记录边界解密的关键两步3.1 单字节异或密钥的三种定位方法如果加密方式确实是单字节异或定位密钥有非常成熟的思路按难度从低到高排第一种频率统计。把文件读进来统计每个字节出现的次数找出最高频的字节。在 gbbq 这类紧凑二进制文件里大量保留位和填充字段是 0x00异或之后会统一变成同一个值。以 0xAA 作密钥为例明文 0x00 会变成密文 0xAA密文里高频出现的 0xAA 就暴露了密钥。第二种已知明文反推。如果你知道某段明文比如某只股票的代码以 ASCII 形式存放6 位数字 000001那么密文里应该会出现连续的 0x9A 0x9A 0x9A 0x9A 0x9A 0x9B0x30 异或 0xAA 等于 0x9A0x31 异或 0xAA 等于 0x9B。用这个片段在文件里搜索密钥就跟着出来了。第三种也最省事暴力枚举加可读性评分。单字节密钥只有 256 种可能全部试一遍然后用一个简单的规则打分解码结果里可打印 ASCII 越多、数字越多得分越高。下面是可运行版本def score_text(data: bytes) - int: score 0 for b in data: if 32 b 126: score 1 if 48 b 57: score 2 return score def guess_xor_key(raw: bytes, header_len: int 16) - int: body raw[header_len:header_len 4096] best_key, best_score 0, -1 for key in range(256): decoded bytes([b ^ key for b in body]) s score_text(decoded) if s best_score: best_score, best_key s, key return best_key这段脚本不需要任何第三方库把文件路径扔进去就能跑。前提是你要先确认文件头长度header_len传错会影响判断不过可以让脚本对 0 到 64 的头部长度都试一轮取综合得分最高的一组参数。我在自己样本里跑出来的结果是 0xAA这是常见的一个密钥。不同券商定制版本、不同年份的通达信密钥可能不一样但定位方法完全相同。不要死记 0xAA要掌握方法。3.2 记录边界的确认思路密钥解决之后文件会从乱码变成可读的二进制但离拿到表格还差一步找到每一条记录的边界。如果记录是定长的问题就简单。你可以看文件总大小再结合里面明显的位置特征猜测记录长度。我手里的版本解密后的记录结构是 40 字节一条头部 16 字节可以跳过。如果不确定用日期锚点来定位更快。把解密后的数据按 4 字节小端整数读出来如果某个位置的数值落在合理范围内——比如从 1990 年 12 月 19 日到 2030 年对应的大约 0 到 15000 天——就说明这里很可能是一个日期字段以这个位置为锚点前后推字段就能拼出完整记录结构。这里有个小经验A 股的除权除息事件集中在每年 4 到 7 月如果你解码后看到的日期大量落在这个区间基本可以确认格式找对了。3.3 变长记录也不可怕有些版本或者券商定制版的 gbbq 记录不是定长的我曾经在某一个版本上就遇到过。直接用定长解析从某条记录开始后面全乱了代码没问题数据长度不对。变长记录要用锚点扫描法。思路是先在整个解密区里按日期窗口搜索所有可能成为记录头的位置然后对每个候选点做字段合理性过滤能拼出一个合法的日期、代码和比例就认为这条记录成立然后跳过这条记录的实际长度继续往后扫。这种方法比猜固定长度通用得多缺点是慢一点但 gbbq 文件本身不大几秒也能跑完。4. 完整的 Python 解密实现从十六进制到 CSV4.1 配置驱动把版本差异挡在门外经过前面几步格式基本摸清了。我把实现写成了一个可配置的小工具把版本的差异集中到CONFIG字典里。以后碰到新版本只要改自己样本对应的参数不用改解析逻辑。import struct import csv import datetime from pathlib import Path CONFIG { xor_key: 0xAA, header_len: 16, record_len: 40, date_base: datetime.date(1990, 12, 19), } def decode_with_key(raw: bytes, key: int) - bytes: return bytes([b ^ key for b in raw])为什么日期基线要设成 1990 年 12 月 19 日因为这是国内交易所早期常见的时间计数起点我在验证本地样本时发现日期字段代表的正是从这一天开始的天数。如果你的文件不对换成 1990 年 1 月 1 日或者直接按 Unix 时间戳秒处理也就多试两次的事。4.2 记录解析函数40 字节记录里字段分布大概是这样市场代码 1 字节、股票代码 6 字节 ASCII、除权除息日 4 字节小端整数、送股、转增、配股、配股价、每股分红各 4 字节浮点数其余是保留位。def parse_record(rec: bytes, cfg: dict) - dict: market rec[0] code rec[1:7].decode(ascii, errorsignore).strip(\x00) date_days struct.unpack(i, rec[7:11])[0] send_ratio round(struct.unpack(f, rec[11:15])[0], 4) transfer_ratio round(struct.unpack(f, rec[15:19])[0], 4) rights_issue_ratio round(struct.unpack(f, rec[19:23])[0], 4) rights_issue_price round(struct.unpack(f, rec[23:27])[0], 4) cash_div round(struct.unpack(f, rec[27:31])[0], 4) event_date cfg[date_base] datetime.timedelta(daysdate_days) return { market: market, code: code, date: event_date.strftime(%Y-%m-%d), send_ratio: send_ratio, transfer_ratio: transfer_ratio, rights_issue_ratio: rights_issue_ratio, rights_issue_price: rights_issue_price, cash_div_per_share: cash_div, }需要解释一下字段含义。send_ratio是每股送股比例如果记录里是 0.3代表每 10 股送 3 股transfer_ratio是转增rights_issue_ratio是配股比例cash_div_per_share是每股现金分红记录 0.228 就代表每 10 股派 2.28 元。4.3 主体函数和 CSV 落地主体函数负责把文件读进来、异或解密、按记录长度循环解析。这一步的容错很关键万一文件中间有脏数据不能因为一条记录就让整个解析挂掉。我在真实文件里遇到过记录尾部有异常字节的情况所以在循环里加一层 try 会比裸跑稳妥。def parse_gbbq(path: str, cfg: dict CONFIG) - list: raw Path(path).read_bytes() decoded decode_with_key(raw, cfg[xor_key]) records [] offset cfg[header_len] while offset cfg[record_len] len(decoded): rec decoded[offset: offset cfg[record_len]] try: records.append(parse_record(rec, cfg)) except Exception: pass offset cfg[record_len] return records def to_csv(records: list, out_path: str) - None: if not records: return fields list(records[0].keys()) with open(out_path, w, newline, encodingutf-8) as f: writer csv.DictWriter(f, fieldnamesfields) writer.writeheader() writer.writerows(records) if __name__ __main__: records parse_gbbq(D:/zd_ts/T0002/gbbq) print(f解析出 {len(records)} 条记录) for rec in records[:5]: print(rec) to_csv(records, gbbq_dump.csv)运行之后你会得到一个结构清晰的 CSV列名分别是market、code、date、send_ratio、transfer_ratio、rights_issue_ratio、rights_issue_price、cash_div_per_share。4.4 我本地跑出的效果在我自己的样本上脚本输出大概是这样的codedatesend_ratiotransfer_ratiorights_issue_ratiorights_issue_pricecash_div_per_share0000012022-04-2900000.2280000012021-04-2900000.1850000022022-07-1400000.976这些数值和公开的除权除息公告能对应上尤其是平安银行 2021 年度每 10 股派 2.28 元这一条除权除息日是 2022 年 4 月 29 日解析结果完全吻合。当然你手上不同版本的文件字段偏移可能有差异解析结果要以你自己的文件为准。5. 交叉验证解析结果拿一只你熟悉的股票说话5.1 验证的正确姿势解密脚本跑通之后千万别直接拿去入库先做交叉验证。方法很简单找一只有明确分红历史的股票比如平安银行 000001。它 2021 年度的分红方案是每 10 股派 2.28 元不送股、不转增、不配股除权除息日是 2022 年 4 月 29 日。在解析结果里过滤code 000001你应该能找到一条或多条记录其中一条就是cash_div_per_share约等于 0.228日期是 2022 年 4 月 29 日。如果这条记录对上说明密钥、头部长度、记录长度、字段偏移、日期基线全都没问题。这一条记录比任何理论分析都有说服力。5.2 对不上号时的排查表如果验证失败按下面的顺序排查基本能定位问题现象可能原因处理方式解码后依然乱码异或密钥不对用第 3 章的频率统计或暴力枚举重新定位日期字段出现负数或离谱年份日期不是从 1990 年 12 月 19 日开始的天数换成 Unix 时间戳或 1990-01-01 基线记录数量对但内容错乱记录起点偏移不对重新定位 header_len前一部分正常后面全部混乱记录长度设置不对用锚点扫描法重新计算 record_len5.3 容易被忽略的边界情况验证的时候还要注意几点。第一新股可能没有任何除权记录。如果解析出来的文件里找不到某只次新股的 gbbq 记录不一定是解析错了而是它确实还没有过除权事件。第二分红预案不等于除权记录。上市公司公告分红预案是 4 月份真正实施除权除息可能要等到 6、7 月。gbbq 里只记录已经实施的除权除息事件。第三验证日期时要以除权除息日为准不是股权登记日也不是公告日。这三个日期在某些情况下会差一两天。6. 股本变迁数据在量化里的几种实在用法6.1 自己构建复权因子链拿到一个干净的 gbbq CSV 之后第一个能干的实事就是自己复权。复权不是靠加密的软件功能它的数学基础就是除权参考价公式除权参考价 前收盘价 - 每股现金分红 配股价 × 每股配股比例/1 每股送股比例 每股转增比例 每股配股比例有了这个公式再配合日线数据你可以从最新交易日往前回溯把历史价格按每个除权事件逐步换算生成前复权因子。后复权则是反过来处理。这份因子链完全在自己手里不依赖任何商业接口而且随时可以重新生成。6.2 高送转主题的选股池高送转是 A 股市场长期被关注的题材。从 gbbq 数据里可以很容易提取出历史高送转事件把send_ratio transfer_ratio大于等于 0.5 的记录筛出来就得到了一份每 10 股送转 5 股以上的事件清单。配合当时的市值、行业、股价位置可以构建一个简单的事件因子研究样本。这种数据自己可控想怎么清洗就怎么清洗。6.3 除权除息事件的多因子回测除权日本身是一个天然的事件日。有些策略会研究除权除息后的填权行情在回测框架里把每一条 gbbq 记录当作一个事件点统计事件日前后 5 个、20 个交易日的收益率分布就能验证是否有统计意义上的超额收益。需要注意的是做这类回测时要剔除上市初期和长期停牌的特殊样本否则结果会带着明显的幸存者偏差和停牌噪声。6.4 本地备份与跨平台迁移把 gbbq 解密成 CSV 后这一份数据就成了脱离通达信的资产。我通常的做法是把它转存成 SQLite 表字段和 CSV 保持一致按代码加索引查询速度很快也方便和日线数据 join。以后无论换什么行情软件、构建什么回测系统只要带上这份历史记录复权逻辑就能完整迁移过去。7. 实操过程中的版本差异与备份经验7.1 不同版本和券商定制版有明显差异我得提醒一句gbbq 的格式不是一成不变的。不同版本的通达信甚至不同券商的定制版本都可能在头部长度、记录长度、字段布局上有差异。我写这篇文章时用的样本是 40 字节定长记录、密钥 0xAA这只是我本地版本的情况。如果你在自己的文件上跑出来对不上优先怀疑版本差异不要直接怀疑方法。用第 3 章和第 5 章的方法重走一遍参数调整一下就能适配。7.2 升级前先留档是最省事的习惯这是我在折腾数据文件时最重要的经验之一。每次升级通达信之前先把 T0002 整个目录复制一份压缩包标好日期。这样做的直接好处是版本升级后如果 gbbq 格式变了解析脚本跑不通你可以立刻对比新旧两个文件找出变化点而不是对着一个陌生格式从头猜。我遇到过不止一次升级前没备份升级后脚本全废的情况。后来养成这个习惯每次都能在十分钟内定位出差异比任何技巧都管用。7.3 关于合规的一点提醒最后说一句实在话。gbbq 文件的解析针对的是你自己电脑上已经安装的软件数据一般用于个人学习、研究数据备份。如果你拿解析出来的数据去做商业分发或者借此破解软件的付费功能那就越界了。技术探索要有边界别给自己找麻烦。我的做法是分析完的脚本和生成的 CSV 都留在本地研究环境里不对外传播。安全、干净反而能长期玩下去。我现在的习惯是每次通达信升级前先把 T0002 目录打个压缩包标好日期扔进网盘。版本一换脚本跑不通拿新旧文件一对比差异马上清楚。这一招比任何搜索技巧都管用。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑