Settlement And Cash Movement
ODTS 14 — 结算和资金流转:钱实际怎么流动
业务问题
交易簿记了。定价引擎估值了。但这两件事一分钱也没有动。结算是钱实际易手的地方。
簿记 → 估值 → 保证金计算 → 资金流转 (CASH MOVEMENT) → 结算
↑
真正的 P&L 发生在这里
如果交割指令 (settlement instruction) 错了:
- 客户收到错误的金额
- 交易台必须手动纠正(冲账)
- 如果错误被晚发现,交易台可能要承担利息
一笔交易产生哪些资金流转
1. 权利金 (Premium)——期权
时间:T+1 或 T+2
内容:客户付权利金给交易台(或反向)
金额:期权权利金 × 乘数
系统:产生一条资金划转指令
2. 融资付款 (Funding Payments)——TRS
时间:每个定盘周期(月/季度)
内容:客户付融资款 = (SHIBOR + spread) × 名义本金 × 天数/365
金额:可变,取决于 SHIBOR
系统:ContractHelper 计算,产生资金指令
3. 分红支付 (Dividend Payments)——TRS
时间:标的股票的除息日
内容:交易台将收到的分红付给 TRS 客户
金额:每股分红 × 股数
系统:ContractDividendHelper 计算
4. 盯市追保 (MTM Margin Calls)
时间:每日(如 CSA 要求)
内容:如果头寸向不利方向变动,交易台向客户追保
金额:自上次追保以来的 MTM 变动
系统:hedging-as 保证金引擎计算
5. 到期结算 (Settlement)
时间:到期日或终止日
内容:交易的最后现金结算
金额:最终 MTM 价值
系统:计算后加入批量结算
系统如何处理资金流转
资金划转模块
~/odts1/eds-web-app/src/com/cicc/edsBoot/cash/
├── CashTransferController.java — REST API
├── CashTransferService.java — 业务逻辑
├── CashTransferGenerator.java — 指令生成
└── CashTransferDao.java — 数据库访问
资金划转表:
CashTransfer {
id: CT20250716001
contractId: SNOWBALL202507001
direction: PAY / RECEIVE
counterparty: CLT0001
amount: 1,230,456.78
currency: RMB
reason: FUNDING_PAYMENT / PREMIUM / MARGIN / DIVIDEND / SETTLEMENT
status: PENDING / CONFIRMED / SETTLED / FAILED
settlementDate: 2025-08-16
instructionRef: "CMS-20250816-001" (清算系统引用)
}
资金流转工作流
T-1 日:系统计算资金流转
→ CashTransferGenerator.createInstructions()
→ 在 CashTransfer 表中创建条目,状态 = PENDING
T 日:运营审核
→ 用户在 new-edsweb 中打开资金划转页面
→ 审核所有待处理指令
→ 批准/拒绝每一条
→ 批准后的指令状态 = CONFIRMED
T+1 日:结算 — IPMP 转账
→ Odyssey cash-manager-service 将 CONFIRMED 指令组装成 XML 报文
→ 通过 IPMP 平台发起银行间转账
→ 轮询转账状态直至完成
→ 结算成功:状态 = SETTLED
→ 结算失败:状态 = FAILED(需要人工干预)
为什么运营人工审核: 系统没有 100% 的准确率。边缘情况(公司行为、到期交易、提前终止)仍然会产生错误金额。运营在钱实际转出前进行拦截。
运营审核的实际工作负荷:一个活跃的中型交易台每天产生 20–50 条资金指令。每条指令运营需要检查:收款方是否正确、金额是否合理、结算日期是否匹配合同。每条约 2–5 分钟。每天就是 40–250 分钟——1–4 小时。在交易量高峰期(如月末、季末结算日),指令数可以翻倍到 80–100 条,运营审核变成专职工作。这就是为什么每个交易台至少需要 1 个运营人员全职处理结算——不是因为他们技术含量高,而是因为每笔钱都需要人眼确认后才能转出。
实际转账系统:IPMP(资金划拨平台)
IPMP 是 ODTS 对接的银行间资金划拨平台,负责将运营确认的资金指令实际发送到银行执行。ODTS 通过 Odyssey cash-manager-service 与 IPMP 交互,协议为 XML over HTTP。
转账指令报文结构
<ipmp>
<head>
<reference>CT20250716001</reference> <!-- 流水号,对应 CashTransfer.id -->
<busiCode>1001</busiCode> <!-- 业务类型编码 -->
</head>
<body>
<ftOrderNo>CT20250716001</ftOrderNo> <!-- 转账单号 -->
<urgent>02</urgent> <!-- 02=普通,01=加急 -->
<settledAmount>
<date>20250716</date> <!-- 结算日期 -->
<currency>CNY</currency> <!-- 币种 -->
<amount>1230456.78</amount> <!-- 金额 -->
</settledAmount>
<orderingCustomer>
<account>3100xxxxxx</account> <!-- 付款方账号(交易台) -->
<bankCode>ICBKCNBJ</bankCode> <!-- 付款方银行 SWIFT -->
<name>中金公司</name>
<publicBankCode>102100000004</publicBankCode> <!-- 付款行联行号 -->
</orderingCustomer>
<beneficiaryCustomer>
<account>6200xxxxxx</account> <!-- 收款方账号(客户) -->
<bankCode>COMMCNSH</bankCode> <!-- 收款方银行 SWIFT -->
<name>某某投资有限公司</name>
<publicBankCode></publicBankCode> <!-- 收款行联行号(可选) -->
</beneficiaryCustomer>
<summary>期权权利金-SNOWBALL202507001</summary> <!-- 摘要 -->
</body>
</ipmp>
数据来源:TransMoneyRequestMessage.java (VO),MoneyTransfer.java (DTO)
响应报文
<ipmp>
<head>
<reference>CT20250716001</reference>
<busiCode>1001</busiCode>
</head>
<body>
<relatedReference>IPMP2025071600001</relatedReference> <!-- IPMP 流水号 -->
<queryReference>QR2025071600001</queryReference> <!-- 查詢引用号 -->
<ftOrderNo>CT20250716001</ftOrderNo>
<instructionStatus>PROCESSING</instructionStatus> <!-- 初始状态 -->
<successAmount>0</successAmount>
<failureAmount>0</failureAmount>
<unknownAmount>0</unknownAmount>
</body>
</ipmp>
转账轮询机制
IPMP 转账是异步的——提交指令后系统返回一个 queryReference,Odyssey cash-manager-service 通过轮询获取最终结果:
1. 提交: POST /ipmp/message (XML)
→ 返回: relatedReference + queryReference + instructionStatus=PROCESSING
2. 轮询: 每隔 N 秒查询状态 (config: statusPollIntervalSeconds)
→ 查询参数: bankCode + queryReference
→ 返回: StatusBody { requestBusiCode, relatedReference, status, code, remark }
3. 最终状态:
→ SUCCESS: successAmount = 总金额, successTime = 完成时间
→ FAILED: failureAmount = 失败金额, remark = 原因
→ UNKNOWN: 不确定(需要人工核实)
转账失败处理
IPMP 转账可能部分成功(部分金额成功、部分失败)。响应中的 successAmount / failureAmount / unknownAmount 三个字段分别记录三种状态的金额。运营需要根据 remark 判断原因并决定是否重新发起。
IPMP 转账超时事故: 某次市场剧烈波动日,IPMP 平台因银行端交易量激增导致转账处理时间从通常的 30 秒飙升到 10+ 分钟。Odyssey 的轮询机制在默认超时设置下反复重试,产生了同一个转账请求被提交多次的 “重复转账”风险。运营在 2 小时内手动处理了 40+ 条转账状态不确定性案例,其中 3 条因重复提交导致实际转出金额翻倍。多转的资金花了 3 个工作日才追回。事后增加了幂等性检查——IPMP 端使用
reference字段去重,Odyssey 端将超时从 30 秒延长到 120 秒。
银行信息映射
系统维护两张银行信息表,用于将内部银行代码映射到 IPMP 的银行编码:
- EdsBank:
bankid(内部ID) →bankname(名称) →bankabbr(缩写) →ipmpbankcode(IPMP编码) - EdsCnaps:
cnapsPrefix→cnapsCode(CNAPS号) →branchNam(支行名) →bankNam(总行名) →ipmpBankCode
这两张表解决了”交易员选银行→系统找到对应 IPMP 银行编码”的映射问题。
通知系统:JUMS(邮件/SMS 通知)
资金划转完成后,Odyssey cash-manager-service 通过 JUMS 发送通知给相关方。JUMS 是一个模板化的通知服务,支持邮件和短信两种渠道。
模板邮件通知
JUMS 使用预定义的模板发送邮件,通过 template_id 指定模板,template_para 传递填充参数:
{
"aud_email": [{
"instance": "email",
"group": { "to": ["client@example.com", "ops@cicc.com"] }
}],
"template_id": 1001,
"template_para": {
"clientName": "某某投资有限公司",
"amount": "1,230,456.78",
"currency": "CNY",
"settlementDate": "2025-07-16",
"tradeRef": "SNOWBALL202507001"
}
}
邮件+SMS 组合通知
对于需要即时触达的场景(如 Margin Call),JUMS 支持同时发送邮件和短信:
{
"aud_email": [{ "instance": "email", "data": ["ops@cicc.com"] }],
"aud_sms": [{ "instance": "sms", "data": ["13800138000"] }],
"msg_email": [{ "subject": "Margin Call 通知", "text": "..." }],
"msg_sms": [{ "content": "您的账户需要追加保证金..." }]
}
JUMS 配置
JUMS 通过 Spring Boot application.yml 配置:
jums:
url: https://jums.internal.cicc.com/api
channelKey: odts-cash-channel
masterSecret: ****
templateUrl: https://jums.internal.cicc.com/templates
templateId: 1001
数据来源:JUMSConfig.java, EmailTemplateNotice.java, EmailSmsNotice.java
结算日历
交易在特定日期结算。系统必须知道结算日历:
日历:香港、上海、伦敦、纽约
— 如果结算日落在假日 → 顺延到下一个工作日
— 不同币种在不同日历上结算
— 人民币在上海日历上结算
— 美元在纽约日历上结算
一笔跨币种交易 (USD/CNY) 按以下方式结算:
USD leg:纽约日历
CNY leg:上海日历
如果 NY 关闭但上海开放:USD leg 延迟,CNY leg 正常
结算批处理 (Settle Batch)
结算批处理每天在 EOD 批处理后运行:
1. ComputeSettle: 找出所有今天结算的交易
2. GenerateInstructions: 生成付款指令
3. Match: 与客户的交割指令交叉核对
4. Report: 生成结算报告 (Excel) 给运营
代码:
~/odts1/eds-web-app/src/com/cicc/edsBoot/settle/
├── SettleBatchService.java — 批处理编排
├── SettleInstructionGenerator.java — 生成付款指令
├── SettleMatcher.java — 指令与客户主数据匹配
└── SettleReportGenerator.java — Excel 报告
什么会出错
1. 交割指令不匹配
问题:客户换了银行账户但运营没有更新系统
结果:钱发到了旧账户 → 被退回 → 延迟 1–3 天
修复:人工干预,检查客户主数据
2. 周末/假日时间问题
问题:结算日落在周日
结果:系统生成周五(如果 T+2)或周一(如果 T+1)的指令
— 但天数计算用了 365 天,而不是实际日历天数
— 产生微小金额差异
修复:日历感知的结算日期计算
3. 支付失败
问题:客户银行拒收付款(余额不足、合规拦截)
结果:CashTransfer 状态 = FAILED
— 交易台必须通过替代方式收款
— 如果超过 N 天仍未支付,构成违约事件
修复:运营联系客户的资金部门
4. 公司行为结算
问题:TRS 标的股票在结算日发放分红
结果:分红现金流和结算现金流交叉
— 交易台在一个 leg 上向客户支付分红
— 交易台在另一个 leg 上接收交易结算款
— 净额结算应该合并它们,但系统不跨 leg 轧差
— 客户收到两笔资金流转而不是一笔轧后金额
修复:系统不做净额结算(已知限制)。运营手动调整。
结算错误的累积成本: 以上四种错误场景出现的频率——交割指令不匹配约每月 1–2 次、时间问题每季度 1 次、支付失败每月 2–3 次(大部分当天解决)、公司行为结算每月 1–2 次(看分红/拆股频率)。大部分错误在当天被运营发现并修复,但剩下 5–10% 的错误需要 2–5 个工作日来解决。这些”遗留错误”每年 10–20 次,平均每次造成 2–5 人时的对账工作量。以一个错误涉及运营、销售、客户的 3 方沟通,每次沟通 30 分钟计算,每年结算排错的总时间是 100–200 人时——约 0.5–1 人月。
“未对账”问题
所有结算完成后,交易台的现金余额应该与银行对账单一致。实际上:
系统显示:现金余额 = 150,234,567 RMB
银行显示:现金余额 = 150,234,567 RMB ←(很少见)
更常见:
系统显示:现金余额 = 150,234,567 RMB
银行显示:现金余额 = 149,800,000 RMB
差异: 434,567 RMB(未对账)
为什么有差异?
- 某条结算指令延迟了 1 天
- 扣了一笔手续费(银行手续费、清算费)
- 收到了一笔分红但尚未记录
- 外汇兑换的汇率与预期不同
对账 (Reconciliation) 由运营每周进行一次。他们将系统资金流转与银行对账单对比。任何超过 1 万 RMB 的差异都需要调查。
交易台说: “对账很无聊但必不可少。第 1 天发现 10 万的差异很容易修复。同样的差异 6 个月后才发现,要花 2 周来追查。”
对账的实际人力成本: 一个运营人员每周花半天到一天做对账。对于一个有 50+ 存续合约、20+ 客户的交易台,每月的资金流转条数约 300–600 条。运营需要对每条流转确认银行端是否已处理。每月对账时间约 2–4 天。每年的对账人力投入约 1–2 人月。这笔成本是固定的——不管市场好不好、交易量涨不涨,对账都得做。这也意味着当交易量翻倍时,对账工作量并不是翻倍——资金流转条数增加但银行对账单条目不线性增长。系统缺少的是自动对账功能:将 CashTransfer 表的记录与银行端下发的电子对账单自动匹配。
已知局限
| 问题 | 现状 | 影响 |
|---|---|---|
| 转账无净额结算 | 每条 CashTransfer 独立提交 IPMP | 客户一天内收到多笔转账而非一笔轧后金额 |
| JUMS 通知不可靠 | 通知发送后无回执确认 | 运营不知道客户是否收到 Margin Call 通知 |
| 状态轮询延迟 | IPMP 转账结果通过固定间隔轮询获取 | 从提交到确认最长可能延迟数分钟 |
| 银行信息手工维护 | EdsBank/CNAPS 映射表手动更新 | 新银行接入慢,可能使用了错误的联行号 |
| 无自动对账 | 运营每周手动核对银行对账单 | 差异发现晚,追回困难 |
这意味着什么: 结算 bug 会让交易台亏钱——如果你发错了金额,交易台先垫付差额再向客户追偿。把结算做对比你想象的重要。1bp 的定价错误是零头。10 万 RMB 的结算错误就是实打实的 10 万 RMB。
真实结算损失案例——重复支付: 某次到期结算,系统在 T 日生成了 Snowball 产品的到期返款指令。运营审核后提交 IPMP。由于 IPMP 响应延迟,系统认为提交失败并重新生成了同一笔指令。运营看到两条”待审核”指令,以为是两笔不同的结算(到期本金 + 最后一次票息),都批准了。结果是本金被支付了两次。客户收到双倍金额后没有主动退回——直到 3 天后对账时发现银行余额与系统记录差了 2000 万。交易台联系客户,客户表示”需要内部审计确认才能退款”,又拖了 5 个工作日。这 2000 万在 8 天里处于”已付但未确认”状态——如果客户在这期间违约,交易台就是一笔坏账。事后修复是增加了重复指令检测逻辑:相同的
contractId+reason+amount在 24 小时内只能创建一条。