Learning
VOL. VII · NO. 53 · OTC Derivatives · 19 JUL 2026

Modernization 2021 2024

OTC 衍生品 · 19 JUL 2026 · 12 min read · 2,771 words
· · ·

ODTS 04 — 现代化 2021–2024:Strangler Fig、微服务实验与系统现状

业务背景 2021–2024

到 2021 年,中金 OTC 衍生品交易台不再是创业团队了。它是一个中国 OTC 衍生品 top-3 做市商,和中信、广发及其他主要券商直接竞争。

业务压力有四个来源:

1. 利率压缩 (Margin Compression)

雪球 15% 票息的黄金时代结束了。到 2022 年,雪球票息只有 5–7%。交易台需要来维持收入。

2018 年收入模型:
  雪球票息: 15%
  对冲成本: 8%
  利差: 名义本金的 7% → 10 亿 RMB 组合产生 7000 万 RMB

2023 年收入模型:
  雪球票息: 6%
  对冲成本: 4%
  利差: 名义本金的 2% → 10 亿 RMB 组合产生 2000 万 RMB
  (同样收入需要 3.5 倍的名义本金)

系统影响: 交易台需要用同样的团队处理更多交易、更快。自动化不再是可选的——是必须的。

2. 新竞争对手

到 2022 年,几家新进入者用现代技术栈(云原生、微服务、Kafka、React)从零搭建了 OTC 衍生品系统。中金 2016 年起步的技术栈开始显得老了。

3. 人民币产品扩张

从 2022 年起,交易台大力扩展到人民币计价的结构化产品——瞄准中国境内投资者(之前大部分量是通过香港的外资投资者)。

对系统的意义:

  • 境内产品有不同的监管要求(CSRC vs SFC)
  • 结算通过中央结算公司 (CCDC),不是 Euroclear
  • 客户准入 (KYC) 需要中国内地特定的流程
  • 系统一直是为香港/港币产品建的——加人民币意味着全新工作流

4. 2022 年雪球危机

2022 年,A 股又跌了。中证 500 从 7300(2021 年 12 月)跌到 5200(2022 年 4 月)——跌幅 29%。很多从 2021 年就开始跑的雪球敲入了 (knocked in)

这是第二次生产危机:

  • ~2000 个雪球合约需要障碍事件处理 (barrier event processing)
  • EOD 批处理跑了 4+ 小时(正常 90 分钟)
  • 初始保证金/变动保证金 (IM/VM) 飙升——有些对手方付不出来
  • 系统的障碍事件处理器从未处理过一天 200 个事件(它是为一天 10–20 个设计的)

什么炸了: hedging-as/service/eod/BarrierCheckStep.java——原始实现是单合约逐个检查障碍。2000 个合约 × 5+ 个障碍位 = 10000 次检查 × ~100ms = 1000 秒。修复方案:并行化(Java parallel stream)。

业务代价: 交易员在关键时刻拿不到准确的保证金数据。对手方在等催缴通知 (margin call),系统在慢跑。一个对话:

交易员:“为什么 EOD 还没跑完?” 开发:“有 2000 个雪球要检查障碍事件,系统设计时没想过这种情况。” 交易员:“所以我们现在给对手方发催缴是盲发的?” 开发:”……差不多。”

又学到的教训: 每一次大的市场事件都会把系统的某个部分搞崩。系统从未在规模上被测试过——每次都是从生产里发现瓶颈。障碍处理应该默认并行,而不是串行。

两个前端的由来

这是一个关键的文化问题。

2021–2022 年,团队分成了两组:

A 组(“eds”团队):

  • 维护 new-edsweb(Vue 2 + Element UI)
  • 拥有 eds-web-app(Spring Boot)
  • 专注交易簿记、确认书、报告

B 组(“期权”团队):

  • 建了 odts-option-web(Vue 2 + KoiUI)
  • 维护 hedging-as(定价引擎)
  • 专注风险管理、Greeks、对冲

为什么另建一个前端: B 组想要”专业”的风控 UI。A 组的 new-edsweb 专注交易操作(簿记,不是风控)。KoiUI 组件库有更好的图表和仪表盘。两个组在不同办公室(上海 vs 香港)。

结果: 同一个后端,两个前端,没共享组件。两个团队各自建了:合约列表页、保证金展示页、交易搜索和过滤、Excel 导出。

重复现在开始实实在在地烧钱了。每个功能都要建两遍。

2022 年的解决方案尝试: 开了个会。“能不能共享 Vue 组件?“技术答案是”可以”,但实际答案是”不行”——两个项目有不同的构建流程、不同版本的 Vue 2、不同的设计语言 (Element vs KoiUI)。重构需要数周,大家都很忙。两个前端继续。

真正的原因: 没有任何一个团队的领导有权力命令另一个团队”你必须用我们的组件库”。没有跨团队的技术负责人。组织架构决定了系统架构。

Strangler Fig 迁移 (2022–2023)

到 2022 年,JSP 前端 (edsWeb) 已经”维护模式”3 年了。但它没死掉。用户还在用它做:历史交易查询(Vue 应用没有完整历史)、管理功能(用户管理、系统配置)、未迁移的报告。

方法: 不一次性迁移所有功能。新功能写 Vue,逐个扼杀 (strangle) 老的 JSP 页面。

  1. 对同一功能加新 Vue 页面
  2. 更新路由指向 Vue 页面
  3. JSP 页面保留(但没人用了)
  4. 6 个月后没人抱怨,删除 JSP 页面

JSP 页面的消亡:

  • 2021: ~150 个 JSP 文件
  • 2023: ~80 个(报告页面,它们反抗最激烈)
  • 2024: ~50 个(有些永远不会死,见下文)

幸存页面: 自定义报告(用户热爱他们的 Excel 导出)、管理功能、系统日志查看器、小工具。

为什么它们活下来了: 报告是针对特定用户高度定制的。每个用户有自己的 Excel 格式。迁移意味着重新协商 20+ 个报告格式。管理功能很少用——不值得迁移。有些经理喜欢旧 UI,因为”我知道东西在哪”。

教训: 你可以扼杀一个单体应用,但你扼杀不了用户的 Excel 报告。自定义报告是遗留系统的小强——它们能挺过一切。

Odyssey 实验 (2023)

到 2023 年,团队决定现代化服务层。计划:把功能从 eds-web-app 单体重构到独立微服务。

想法是这样:

eds-web-app (单体)               Odyssey (微服务)
     │                                │
     ├── 簿记 ──────────────────→  ├── booking-svc
     ├── 确认书 ────────────────→  ├── confirm-svc
     ├── 保证金展示 ────────────→  ├── margin-svc
     └── 报告 ─────────────────→  └── report-svc

实际发生了什么:

“Odyssey”项目建了 3 个微服务,但都没有真正完成:

~/odts1/odsy-book-svc/      — 簿记服务(部分完成)
~/odts1/odsy-core-common/   — 共享领域模型
~/odts1/odsy-confirm-svc/   — 确认书服务(部分完成)

为什么叫 Odyssey(奥德赛): 名字是激励性的——漫长的回家之旅。后来变成了讽刺。

交付了什么: 共享领域模型(一堆类,不是运行的代码)、一个能处理简单股权互换的簿记服务、一个包装了现有确认书生成器的确认书服务。

没交付什么: 完整的簿记工作流(不能处理复杂产品)、与 hedging-as 的集成(仍然调老旧的 ActiveMQ 队列)、微服务的新前端(用户还是用 new-edsweb/odts-option-web)。

为什么卡住了:

  1. 业务不会停下来等架构。 Odyssey 在建时,交易台在催新产品(Bond TRS、Daily Accrual、Phoenix)。这些需求建在旧单体里,因为”等不了 6 个月 Odyssey”
  2. 团队分裂。 部分开发做 Odyssey,其他做需求。需求团队有更紧急的工作=更多资源。Odyssey 变成了”下班后”项目
  3. 微服务没有解决真正的问题。 真正的问题不是单体——是两个前端。但没人想讨论合并前端(太政治了)
  4. ActiveMQ vs Kafka。 Odyssey 想要 Kafka。现有系统用 ActiveMQ。新旧集成需要一个桥。没人想建和维护

事后的判断: Odyssey 是个好想法,但在错误的时间执行了。应该把精力花在:合并两个前端为一个 SPA 共享组件、用 Kafka 替换 ActiveMQ、加 CI/CD 和自动化测试。微服务应该是最后一步,而不是第一步。

EOD 批处理:系统最关键的路径

到 2023 年,日终批处理 (EOD batch) 处理:

步骤功能2021 耗时2023 耗时
市场数据刷新获取价格、波动率、利率5 分钟5 分钟
Fixing处理所有浮动端的引用价10 分钟15 分钟
公司行为应用分红、拆股等5 分钟10 分钟
障碍检查检查所有路径依赖产品的敲入/敲出15 分钟30 分钟
估值重新估值全部 ~5000 合约30 分钟60 分钟
Greeks计算所有合约的 Greeks10 分钟20 分钟
保证金计算 IM + VM10 分钟20 分钟
资金变动生成支付指令5 分钟10 分钟
报告SAC、客户报告10 分钟20 分钟
合计~90 分钟~180 分钟

为什么 EOD 越来越慢: 更多合约、更复杂的产品、更多市场数据、串行处理设计(每一步必须等上一步完成)。

瓶颈: 估值和障碍检查在热点路径上都是单线程的。蒙特卡洛模拟不能完全并行化,因为随机数生成器是有状态的 (stateful)。

增量修复:

  • 2022: 并行障碍检查——30 分钟 → 15 分钟
  • 2023: 市场数据缓存(同一个标的不用重新取)——60 分钟 → 45 分钟
  • 2023: 雪球预计算场景网格——45 分钟 → 30 分钟

EOD 失败率:

  • 2019: ~3 次/月(主要是市场数据连接问题)
  • 2021: ~1 次/月(偶尔定价溢出)
  • 2023: ~1 次/季度(已经很稳定了)

尽管慢,EOD 是可靠的。团队知道每一种失败模式。每一步都有重试逻辑。

崩溃点总结:2021–2024 的三个关键时刻

  1. 2022 年雪球危机 → EOD 批处理超时,2000 个雪球同时触发障碍事件。系统设计从未考虑这种并发量。暴露出两个问题:定价引擎的单线程瓶颈、没有熔断机制 (circuit breaker)
  2. Odyssey 实验搁浅 → 团队花了大半年建微服务,但业务不等人。新产品还是写在旧单体里。微服务没交付就停了。根本原因:没有解决真正的问题(两个前端重复才是核心痛点)
  3. 两个前端重复的成本 → 每个功能建两遍,团队无法合并因为组织没有跨组技术负责人。这是整个系统历史上最大的隐性效率损失

系统现状 (2024)

项目全景

项目行数技术栈状态
eds-web-app~15 万Spring Boot 2.x活跃
hedging-as~10 万JFinal活跃
new-edsweb~8 万Vue 2 + Element活跃
odts-option-web~6 万Vue 2 + KoiUI活跃
edsWeb~10 万JSP + jQuery衰落中
accessapp-new~3 万JSP + JDBC已停用
odsy-book-svc~2 万Spring Boot搁浅
odsy-confirm-svc~1.5 万Spring Boot搁浅
jzmq~1 万FIX + ZMQ在用

整个系统: ~56.5 万行生产代码,~35 个项目,20+ 个开发者历时 10 年建成。

未解决的问题

  1. 前端重复:未解决。 两个 Vue SPA,两个设计系统。合并的政治成本 > 维护两者的技术成本
  2. 测试缺口:未解决。 ~56.5 万行代码几乎没有自动化测试。每次部署都紧张
  3. 文档缺口:未解决。 本系列文章是解决方案的一部分。在此之前,系统的业务背景或架构没有任何文档
  4. EOD 调度:未解决。 批处理串行跑 3 小时。大部分日子可以接受,但波动的日子交易台想要盘中估值 (intraday valuation),EOD 不是为此设计的
  5. 中英文混写: 代码、注释、Protobuf 定义中英文混杂。变量名用拼音。非中文母语的开发上手困难

ODTS 系列完整。如果你已经读到这,你对这个系统的历史和架构的了解已经超过了大多数工作过 2 年的开发。代码在 ~/odts1/ 等着你。