技术博客

清算文件解析:交易所和结算公司下发的为什么至今还是定长文本

第 43 / 141 篇 · 2026-04-28 · secfin · 清算文件解析工程实践

做券商清算的第一课往往不是算法,是文件。每天收盘后,中国结算和交易所往下发一堆清算文件:成交明细、清算明细、资金应收应付、证券变动、对账文件。打开看,没有 JSON,没有 Protobuf,是 GBK 编码、每条记录固定字节长度的文本文件,行间用换行分隔,金额以"分"为单位的整数存放。新人第一反应是"这什么年代的东西",待久了才明白这个格式活得比大多数架构都久,是有原因的。

定长文本的工程账

定长格式的优点是定位与校验都变成 O(1):每条记录 $L$ 字节,第 $k$ 条记录从字节偏移 $(k-1)\times L$ 开始,文件总字节数除以 $L$ 就是记录数,尾部多一个字节都能立刻发现截断。配套下发的控制文件(通常一个汇总文件,含记录数、各类金额合计、文件大小和校验位)给了第二重核对:解析时边读边累计金额合计和记录数,读完和控制文件比对,一致才算"文件收全了"。

对比下自描述的变长格式(比如 JSON):解析器要先完整解析才能知道记录数,中间任何一个字符坏了,错误位置之后的全部记录都可能报废;而定长格式坏了第 $k$ 条,前 $k-1$ 条依然是好的,甚至可以按偏移跳过坏行继续解析。对"每晚几万到几十万条记录、错一条都不能悄悄吞掉"的清算场景,这个容错语义值很多钱。

整数分(不存小数)同理:浮点金额在批量计算里会积累舍入误差,而定长字段里 12 位整数加符号位,解析后直接用 64 位整型累加,全程无舍入,合计核对精确到分。GBK 编码是因为业务字段里大量中文(证券简称、备注),历史遗留叠加"改了也省不了几个字节"的成本收益,就一直留下来了。

解析器的正确性设计

清算文件解析器不是普通 ETL,它的输出要进账,所以设计上有一层特殊约束:解析过程本身必须可核对。我见过的成熟实现有三个共同特征。

一是逐字节落表的原始层。解析后的第一步不是业务处理,是把每条原始记录按字段切片原样入库(raw 表),切片只按偏移,不做任何类型转换和语义判断。后续所有业务逻辑都从 raw 表读,好处是文件出了问题可以无限重放:改解析规则后不用重新收文件,重跑 raw 之后的流程就行。

二是合计先行的准入校验。整个文件解析完,先比控制文件的合计数,再比上一环节(比如交易所成交流水 vs 中登清算明细)的总额。合计平不代表明细对——可能错两行金额互相抵消——但合计不平一定有问题。所以合计校验是必要条件,不是充分条件,通过了也只是允许进入下一步。

三是坏行的隔离而非拒绝。控制文件说 50000 条,实际解析出 49997 条,差的 3 条不能简单报错退出。常见原因是文件传输中截断、源系统补丁版本临时改了字段宽度。成熟的处理是把缺失记录数、缺失金额单独挂账报警,同时让其余 49997 条继续走清算——因为清算有截止时间,全停等人工处理的代价更大。但隔离出去的缺口必须在结算启动前解决或在交收文件里显式处理,不能带着洞前进。

为什么迁不动

每隔几年就会有人提议把这些文件换成标准的二进制协议或消息队列直推,提案基本都死在同一堵墙上:对手方不统一。中登、交易所、银行、券商四方,任何一方的格式变更都要其余各方同步改造和回归测试,而清算文件是 T+1 链条的最上游,改出 bug 没有重试的机会——数据错了当天交收就错了。定长文本的"落后"恰恰是所有参与者都读得懂的共同点,迁移的成本收益在风险调整后始终是负的。这也是为什么这一篇排在清算分类的前列:理解文件层的保守,才能理解后面所有清算设计的保守。

清算文件解析流水线:先落原始层,合计先行,坏行隔离下发文件raw 表落库合计校验业务处理GBK 定长文本按偏移切片原样入库对控制文件合计成交流水/对账合计校验是必要条件不是充分条件:平 ≠ 明细对,不平 = 一定有问题坏行隔离:缺 N 条挂账报警,其余继续清算,但缺口不得带入交收金额以"分"存整数,全程整型累加无舍入,合计核对精确到分