问一个简单的问题:周五晚上 22 点,客户通过手机银行转入一笔钱,这笔存款算哪天的?再问一个:周五 14:57 的卖出成交,资金哪天可以取?两个问题的答案都不是看墙上钟能答出来的,因为券商系统里同时存在三套时间:自然日(物理时间)、系统日(账务归属日)、交易日(交易所开市的日子)。很多账务 bug 的本质,是代码里这三套时间用混了。
三套时间各管什么
自然日:物理日历,决定"什么时候计息""什么时候开始算逾期"。利息、融资费用、合约期限都按自然日算——钱在账户里躺了几个 24 小时,就该计几天的息,跟交易所开不开市无关。
交易日:交易所定义的开市日,决定"T+N 的 N 是几天"。A 股卖出资金 T+1 可取,这个 1 是下一个交易日:周五卖出,下一个交易日是周一,所以周一可取;国庆前一天卖出,要等假期结束。所有交收类规则(T+1 可用、T+1 交割、新股中签缴款期限)的 T 都是交易日。
系统日(账务日/工作日期):券商系统内部"当前正在处理哪个营业日"的标志。它不等于自然日——15:00 收市后,物理时间还在今天,但交易系统已经在为"下一个交易日"接单(夜市委托);EOD 批量在日切完成前,账务上还停在"今天"。系统日是数据库里一个全局唯一的当前值,所有账务记录按它归属。
日切这个动作在做什么
日切(Cutoff)不是"把日期加一"这么轻描淡写,它是 EOD 链的最后一个任务,实际做这些事:
- 推进系统日:全局当前系统日从 $D$ 推进到下一个交易日 $D'$。注意是"下一个交易日"而不是"明天"——周五日切推进到周一,长假前日切推进到节后第一天。这一步的瞬间,所有联机交易的账务归属切换。
- 释放在途:上一交易日(或更早)沉淀的在途资金/持仓,凡是到期日的都转成可用。在途记录上都带着"预计可用交易日",日切时按交易日历匹配释放。
- 刷新日级参数:新交易日的涨跌停价(按新收盘价重算)、可充抵保证金折算率版本、费率版本、合约到期检查……日级参数以"生效交易日"为键,日切时把当日的参数集装载成当前集。
- 初始化运行时状态:清空前一交易日的临时表、重置当日序号发生器、加载新交易日的日历上下文。
日切是全局栅栏:它执行期间交易系统必须短暂停新委托(通常几秒到几十秒),因为"委托归属哪个系统日"不能有中间态。日切失败 = 第二天开不了市,所以它是 EOD 里监控等级最高的任务,也是为什么 EOD 总览里说"对账不平不许日切"。
交易日历:一张被严重低估的参数表
交易日历表(trading_calendar)字段朴素:日期、是否交易日、市场(沪深北/港股通各自的日历不同)、备注(节假日名)。但它是全系统的隐式中枢,依赖它的地方比想象多得多:
- T+N 计算:所有"第 N 个交易日"的推算都要查它;
- 日切目标日:推进到"下一个交易日"由它定义;
- 结息与费用:计息区间按自然日,但"结息日遇非交易日顺延"这类规则要查它;
- 报表口径:日报按系统日出,周报月报按交易日集合汇总。
工程纪律是:任何代码不许用"周末/法定节假日"的本地逻辑猜交易日。猜就会被临时调休打脸(调休工作日交易所不开市),被台风临时休市打脸(港交所经常,沪深也有先例),被国庆拼假打脸。交易日历是运营维护的权威数据,节前由交易所公告、运营人员提前录入复核,系统只查不算。沪港通/深港通还要同时维护两套日历,"内地开市但香港不开市"的日子港股通通道关闭,额度计算、交收安排全是按通道日历走的。
三套时间混用的经典事故形态
用自然日算 T+1。 周五卖出,代码写 available_date = today + 1,周六一早客户发现钱显示可取了——实际交收周一才发生,这笔钱取走就是券商垫资。正确写法永远是查交易日历取"下一交易日"。
夜市委托记错系统日。 日切后、开盘前客户下的隔夜委托,物理时间是周日晚 21 点,归属的系统日必须是周一。如果委托表用插入时的物理日期做账务日,周一的清算会漏掉这批委托,或者周五的账里混进周一的委托,两边都对不上。委托表必须显式存系统日字段,由日切后的全局值写入,而不是 sysdate。
计息区间跨日切漂移。 利息按自然日算,但余额取"哪个时点的余额"要按系统日定义(通常取每日日切完成后的时点余额)。计息任务如果在日切中途跑,取到的可能是新旧系统日混合状态的余额,积数就脏了。所以 EOD 总览里计息类任务的位置有讲究:要么严格在日切前(用今天的时点余额),要么严格在日切后(作为新一天的第一批任务),不许骑在栅栏上。
报表取数口径混用。 给监管的"当日成交日报"按系统日汇总,给客户的"收益曲线"按自然日展示,风控的"近 5 日换手率"按交易日窗口算——三个"日"不一样。需求文档里出现"近 N 日"三个字,第一件事就是问清是哪套日。
实现清单
- 全局系统日存库(单行表),所有账务归属读它,不用系统时钟;
- 交易日历按市场维护,节前双人复核录入,提供"下一交易日/第 N 交易日/区间交易日数"三个基础函数,全系统统一调用;
- 业务日期字段分三类命名(
biz_date系统日 /cal_date自然日 /trade_date交易日),代码评审见到裸用sysdate的账务逻辑直接打回; - 日切任务全程留痕:切换前后值、执行耗时、释放的在途笔数,第二天运营早检的第一项就是看日切报告。
三套时间不是券商系统故意复杂化,是业务本身就在三个时间维度上运行:钱按物理时间生息,券按交易时间交收,账按账务时间归属。把三套时间建模清楚,是后面所有批量任务正确性的地基。