Securities Lending Sbl
ODTS 74 — 证券借贷(SBL/转融通):被忽略的一类簿记
为什么需要这篇文档
整个 ODTS 系列写满了期权、远期、互换、收益凭证、雪球这些”主角”产品,但代码库里有一类独立、真实存在、却从未被文档提及的簿记业务:
SBL —— Securities Borrowing and Lending(证券借贷 / 转融通)。
在 eds-web-app 的 sblbooking 模块里,系统维护着一整套证券借贷合约的停牌(suspension)与复牌(resumption)合约簿记,并配套了利率迁移(interest migration)的 EOD 批处理。这说明券商不仅做衍生品,也做”借股票/还股票”的融券业务,而且它有自己独立的生命周期、状态机和日终流程。
一个 PM/BA 如果不知道 SBL 的存在,在做”标的停牌了怎么办""融券利率怎么算”这类需求时,会完全摸错方向。
1. SBL 在代码里的位置
eds-web-app/src/com/cicc/sblbooking/
├── controller/
│ ├── InterestMigrationController.java — 利率迁移接口
│ ├── SblInterestEodController.java — SBL 利息日终
│ └── MigrationNotionalDetailsRequest.java
├── model/ — SBL 合约模型
└── service/
├── ContractGroupInterestServiceImpl.java
├── ContractGroupInterestMigrationService.java
├── ContractGroupAdjInterestMigration.java
├── ContractGroupAdjInterestProcessor.java
├── InterestBean.java
├── FixInterestProcessCallable.java — 固定利率处理
├── FloatInterestProcessCallable.java — 浮动利率处理
└── BatchExecuteCallable.java
合约消息定义(protobuf,hedgingAs/contract):
BeanSblResumptionContract — 复牌合约
BeanSblSuspensionContract — 停牌合约
业务定位: SBL 和衍生品簿记共用一套底层框架(都是 eds-web-app 的 booking 引擎、protobuf 消息、EOD 调度),但是一个独立的业务域——它有自己专属的合约类型和日终逻辑。
2. 一份 SBL 停牌/复牌合约长什么样
BeanSblResumptionContract(复牌)的字段,把”一只股票暂停融券、又恢复融券”这件事完整地建模了:
| 字段 | 含义 | 说明 |
|---|---|---|
contractId / contractName | 合约 ID / 名称 | SBL 合约标识 |
stkId / exchId / fullTicker / tickerName | 标的证券 | 借的是哪只股票、哪个交易所 |
trend | 方向 | 借入 / 借出 |
initCount | 初始数量 | 借了多少股 |
suspensionType | 停牌类型 | 这次暂停属于什么性质 |
suspensionDate / suspensionOccurTime | 停牌时间 | 精确到日期 + 时间戳 |
suspensionOccurDate | 停牌发生日 | |
resumptionType | 复牌类型 | |
resumptionDate / resumptionOccurTime | 复牌时间 | |
resumptionSupensionid | 关联停牌 ID | 把”复牌”挂回”停牌” |
requestid / contracteventid | 请求 / 事件 ID | 工作流追踪 |
contracteventStatus | 事件状态 | 审批/执行状态机 |
业务含义: 一只证券的融券,可能因为监管停牌、券商自身风控、或客户申请而暂停;之后又可能恢复。系统用”停牌合约”和”复牌合约”两条记录来表达,且复牌通过 resumptionSupensionid 指回原始停牌——这是一个事件溯源(event sourcing)式的建模:不直接改一条状态,而是追加”发生了一次暂停/恢复”的事件。
这和 05 文档里讲的生命周期状态机是同一套思想,但 SBL 把它用在”券源可用性”这个维度上,而不是”交易状态”上。
3. SBL 的日终:利率是它真正的复杂性
SBL 表面是”借股票”,真正的业务复杂度在利息:借出的股票要按利率收息,利率可能是固定(FixInterestProcessCallable)或浮动(FloatInterestProcessCallable),而且历史上可能做过利率迁移(老合约按老利率、新合约按新利率)。
代码里的 service 层几乎全是利息相关:
ContractGroupInterestMigrationService/ContractGroupAdjInterestMigration— 合约组利率迁移(批量把一批合约的利率从旧规则切到新规则)ContractGroupInterestServiceImpl— 利率计算SblInterestEodController— SBL 利息日终:每天跑批,按合约算当天利息BatchExecuteCallable+Fix/FloatInterestProcessCallable— 并发处理固定/浮动利率批次
业务痛点: 利率迁移是 SBL 最容易出错的环节。一次全市场利率调整,可能要把成千上万个在途融券合约的计息规则切换到新利率——既要保证新合约用新利率,又要保证迁移时点之前已计提的利息不回滚。这本质上和 08 §5 “EOD 估值切换”是同一类”历史状态不可变、只在边界切”的工程难题。
4. 为什么 SBL 容易被忽略,但又不能忽略
- 被忽略的原因: 它不像期权那样”性感”,文档作者(包括之前的我)天然会聚焦在衍生品主角身上。
- 不能被忽略的原因:
- SBL 的”标的停牌/复牌”直接影响那个标的能不能做衍生品交易——和 09 标的生命周期、72 风控规则引擎里
underlyingControlType是同一只”标的”的不同侧面。 - SBL 利息是券商的真实收入/成本,落在 P&L 里,和 54-MTM-PnL 同一条损益链。
- SBL 的日终批处理和衍生品 EOD 估值抢同一套调度资源——做 EOD 时序编排时(见 09)必须把它算进去。
- SBL 的”标的停牌/复牌”直接影响那个标的能不能做衍生品交易——和 09 标的生命周期、72 风控规则引擎里
5. 内行视角:SBL 是”共用框架、独立域”的典型
SBL 最值得 PM/BA 记住的一点,是它体现了这个系统的整体架构哲学:新产品/新业务不是另起炉灶,而是在 booking 框架上挂一个新的”合约类型 + 一组动作 + 一组日终任务”。
所以当你接到”我们要支持一个新的业务品种”的需求时,正确的类比不是”再写一个全新系统”,而是”像 SBL 那样,在 eds-web-app 里加一组 XxxContract + XxxService + XxxEodController”。这能大幅缩短你和技术对齐需求边界的时间。
自测
- SBL 的”标的停牌”和”复牌”在代码里是用一条状态字段表达,还是用两条合约记录表达?为什么这么设计?
resumptionSupensionid字段的作用是什么?- SBL 真正的业务复杂度在哪一层(簿记?还是利息)?代码里哪些类能证明?
- 为什么做 EOD 调度时序编排时必须把 SBL 利息日终算进去?
- 如果要新增一个业务品种,SBL 模块给你的架构启示是什么?
相关阅读:09-Product-and-Lifecycle-State-Machine.md(状态机)、72-Credit-and-Risk-Rules-Engine.md(标的控制)、54-MTM-and-PnL.md(损益链)