Foundation 2016 2018
ODTS 02 — 奠基 2016–2018:定价引擎与雪球大爆发
业务背景 2016–2017
2016–2017 年是 雪球 (Snowball) 结构化产品的”淘金热”(gold rush)。
到 2016 年,雪球结构化产品是中国财富管理市场最火的东西。几乎所有私人银行、财富管理公司、零售券商都想做。中金的交易台从每月几笔雪球变成了每天 5-10 笔。
为什么雪球会爆发? 这不是一个系统故事,这是一个市场故事:
- 利率在降(2014-2016 年央行多次降息)。理财产品收益率从 4% 降到 3%。投资者需要高收益替代品。
- 中证 500 指数在区间震荡(5000–6500 点)。低波动、没暴跌——对雪球卖方来说完美。
- 中国零售投资者喜欢保本型产品——雪球被包装成”高收益理财”,有保底机制(一定程度的下跌保护)。
- 私人银行搭建了结构化票据 (structured note) 计划——把雪球打包成”增强型存款”(enhanced deposit) 产品。
雪球在业务层面是什么?
投资者说:“给你 15% 年化票息,每个月付,只要中证 500 不跌破期初水平的 75%。如果跌破,我开始亏钱。但如果中证 500 任何一个月涨过 102%,产品提前结束 (early termination),我拿完票息走人。”
银行(交易台)说:“我们通过买入中证 500 期货 (futures) 来对冲 (hedge)。如果市场涨,多余收益归我们。如果市场跌,我们管理头寸来控制损失。”
之所以叫”雪球”(Snowball),是因为:票息会累积 (snowball effect)——如果某个月没触发付息条件(指数跌了),票息会”记住”并在下个月一起付;同时风险也在累积——产品跑得越久,Delta 敞口越大。
商业模式: 银行赚的是支付给客户的票息(比如 15%)和对冲收益 (hedging return) 之间的价差 (spread)。如果市场保持震荡,银行一年能赚名义本金 (notional) 的 3-5%。10 亿 RMB 的雪球组合,年收入就是 3000-5000 万 RMB。听起来很简单?
问题是: 这个商业模式成立,前提是你能准确定价和对冲。如果票息给高了,你亏钱。如果对冲做错了,你爆仓。2017 年,中国没有一家公司有像样的雪球定价引擎 (pricing engine)。所有人都在用 Excel。利润率很高,但没人知道自己真正的风险敞口。
这就是下文的第一个崩溃点:业务规模已经大到 Excel 定价不可持续了。
量化研究员进场
陈博士 2016 年底加入交易台。他的任务:建一个雪球定价引擎,不要在 VBA 里跑 5 分钟。
他的方法:
- 能用解析解 (closed-form solutions) 的地方用解析解(Black-Scholes 对香草期权 (vanilla options) 很快)
- 障碍期权 (barrier options) 用有限差分法 (finite-difference methods)
- 复杂产品用蒙特卡洛 (Monte Carlo)——但只在必须的时候(慢)
- Greeks 用 Bump-and-Reval 方法(暴力但正确)
他设计的定价流水线 (pricing pipeline):市场数据 (Market Data) → 即期价格、波动率、利率、股息率 (Spot, Vol, IR, Div) → 合约参数 (Instrument Parameters) → 收益函数 (Payoff Function) → 估值 (Value) → Greeks
这条流水线到今天仍然在用,基本没变。
构建 hedging-as
定价引擎成为了一个独立项目:
hedging-as/
├── pricing/ — 核心定价逻辑
│ ├── dataCenter/ — 市场数据缓存
│ ├── engine/ — 估值引擎(即期、蒙特卡洛、PDE)
│ └── service/ — 按产品类型的定价服务
├── service/
│ ├── contract/ — 合约领域模型
│ └── eod/ — 日终批处理
└── edslib/
└── payoff/ — 收益函数库
为什么选 JFinal 而不是 Spring Boot
这是 ODTS 历史上最有争议的技术决策。2016 年 Spring Boot 1.x 已经发布。团队选 JFinal 只有一个原因:陈博士讨厌 XML 配置。2016 年的 Spring 还有 XML bean 配置。JFinal 是”约定优于配置”(convention over configuration),几乎不用 XML。
这个决策后来一直困扰着团队。JFinal 没有 Spring 的生态:没有 AOP、没有依赖注入容器 (DI container)、没有自动配置 (autoconfiguration)。每加一个新服务都要继承一个底层的 Controller 类,手工接线。
业务启示: 开发者的开发体验 (developer experience/DevEx) 对技术选型有很大影响——即使选了”错误”的长期方案。陈博士的原话(2023 年回看):“如果现在让我选,我不会选 JFinal。但 2016 年我是一个量化研究员,不是软件工程师。我只要一个能用的。Spring 的 XML 让我生气。JFinal 不挡我的路。“
三大件拆分
2017 年,AccessApp 已经长成了一个有 100+ JSP 页面的单体应用 (monolith),团队已经无法维护。决策:拆成三个项目。
AccessApp(单体,正在消亡)
│
├──→ eds-web-app (Spring Boot,簿记 + 运营)
├──→ hedging-as (JFinal,定价 + 风险)
└──→ edsWeb (遗留 JSP,给历史页面续命)
为什么拆?
| 项目 | 用途 | 框架 |
|---|---|---|
eds-web-app | 交易簿记、确认书、客户报告 | Spring Boot |
hedging-as | 定价、风险、EOD 批处理 | JFinal(计算密集,需要独立扩缩) |
edsWeb | 没人想重写的遗留页面 | 旧 Spring |
集成挑战: 三个项目必须互相通信。eds-web-app 通过 ActiveMQ + Protobuf 调用 hedging-as 定价。eds-web-app 读取 hedging-as 的数据库获取保证金结果。edsWeb 和 eds-web-app 共用数据库。这不是干净的微服务 (microservices)——这是一个演进的单体 (evolved monolith),碰巧部署在不同的 JVM 里。
当时的一个真实对话:
开发:“我们之间不加个 REST API?” 架构师:“过度设计了。先共享数据库。” 开发:“耦合问题怎么办?” 架构师:“同一个团队。Schema 变了,两边一起改。”
这个判断在 2017 年是务实的,但后来证明是错的。Schema 耦合在后来的维护中带来了很多痛苦。
雪球定价引擎详解
雪球定价流程(至今仍在用)值得理解,因为它是系统最重要的路径:
- 合约在
eds-web-app创建(用户输入交易细节) - 定价请求通过 ActiveMQ 发送到
hedging-as hedging-as读取市场数据(即期价、波动率、利率、股息率)HedgingAsPricingController接收请求- 调用
SnowballPayoff.computePayoff() - 估值引擎运行(雪球用蒙特卡洛)
- 计算 Greeks(Delta、Gamma、Vega、Theta)
- 结果存入
TodayContractRaInfo表 - 结果返回
eds-web-app - 用户看到按市值计价 (MTM)
蒙特卡洛的痛点: 雪球需要蒙特卡洛模拟,因为路径依赖(敲出事件 (knock-out) 和记忆票息 (memory coupon))。2017 年,单个雪球估值约 2 秒。EOD 批处理要定价 500 个雪球——这就是 1000 秒(17 分钟)。对于隔夜批处理来说可以接受,但对于盘中”试算”定价来说太慢了。
优化: 团队预计算了场景网格 (scenario grid)。不是对每个雪球跑蒙特卡洛,而是对一组参数网格跑一次,然后插值 (interpolate)。盘中定价从 2 秒降到了 50 毫秒。代价:非标准雪球的精度降低。
交易簿记变革 (2017)
hedging-as 是明星项目,但 eds-web-app 是业务真正生活的地方——运营团队整天在里面操作。
它能做好的:
- 交易簿记表单(有结构的,不是自由输入)
- 交易列表,带搜索和过滤
- Excel 导出(用户很喜欢这个)
- 用户认证与基于角色的权限控制
它做得不好的:
- UI 还是 JSP + jQuery(维护起来很痛苦)
- 没有完善的数据校验(可以簿记一笔不一致的交易)
- 错误消息是 Java 堆栈 (stack trace) 的红色文字
- 没有撤销或确认(删了就是删了)
但不管怎样——系统能用。交易台一天能簿记 50 笔交易不出错。这是当时的胜利。
NDF 业务
2017 年,交易台开始交易人民币 NDF (Non-Deliverable Forward,无本金交割远期)。业务模式:外国投资者(对冲基金、养老基金)想要人民币敞口 (CNY exposure),但不能进入在岸外汇市场 (onshore FX market)。NDF 给了他们合成人民币敞口(以美元结算 (USD-settled))。
客户的现金流: 先交保证金(约名义本金的 10%),到期时收(即期汇率 - 远期汇率)x 名义本金,美元结算。因为客户实际上不能交割人民币。
NDF 定价的挑战: 人民币 NDF 不在公开交易所交易。定价依赖于中国外汇交易中心 (CFETS) 每天 9:15 发布的中间价 (central parity rate)。
如果错估中间价呢?1% 的误差在 1 亿美元名义本金上 = 100 万美元的 P&L 波动。
系统如何应对: FixingManager.java——每天从外部源获取中间价,存入 FixingData 表。定价时,如果当天的中间价已发布就用已发布的,如果是未来日期就用远期曲线 (forward curve) 推算。
NDF 模块是 2017 年初加进 hedging-as 的。这是团队第一次在定价引擎内部建一个产品模块 (product module),而不是另起一个 Excel。建立了一个可以复用的模式:定义收益函数 → 加定价服务 → 加数据源 → 加 Protobuf 消息 → 加前端 UI。
期权业务的增长
香草期权 (vanilla options) 仍然是基本业务。2017 年,交易台在做:
- 单只股票期权(港股和中概股 ADR)——给对冲基金客户
- 指数期权(沪深 300、中证 500、恒指)——给结构化票据发行人
- 彩虹期权 (Rainbow options)(一篮子股票)——给需要股权挂钩融资的企业客户
香草期权的定价并不难(Black-Scholes 解析解)。真正的挑战不是定价,是库存管理 (inventory management)。
比如:如果卖给客户 A 一个 3 个月看涨 (call),同时从客户 B 那里买入一个 6 个月看涨,就产生了一个”期限错配”头寸——空头 3 个月、多头 6 个月。系统需要追踪这个缺口并计算净对冲量。
这个逻辑后来成为 hedging-as 中的 HedgingService.java——告诉交易员”你需要买入/卖出 X 手标的来实现 Delta 中性 (delta-neutral)“。
2018 年:雪球调整(第一次生产危机)
2018 年,A 股市场下跌。中证 500 从 6500 跌到 4500(跌幅 30%)。很多雪球敲入了 (knocked in)——触及了 75% 的障碍价格。
发生了什么:
- 一直在付 15% 票息的雪球开始跟着指数亏钱
- 买了”保本”产品的投资者现在面临亏损
- 交易台必须管理平仓——随着雪球敲出或到期,将对冲头寸平掉
系统的影响: 这是 hedging-as 的第一次生产危机:
- EOD 批处理定价了 800+ 个雪球。蒙特卡洛花了 45 分钟
- 保证金计算对大实值雪球 (deep ITM Snowballs) 返回了负值——团队忘记处理封顶下跌 (capped downside)
- “记忆票息”功能对已经交易了 18+ 个月的雪球计算错误
业务代价: 交易员按照系统给的 Greeks 做对冲,三天后发现 Greeks 是错的。一个真实对话:
交易员:“系统说我雪球组合的 Delta 是 -1500 万。我叫 junior 买了 1500 万期货。三天后 Delta 变成了 +2000 万。我们在这个对冲上亏了 300 万人民币。系统的 Greeks 用的是股灾前的隐含波动率 (implied volatility)。”
这个事件导致了一个业务上的改变:交易员可以手动覆盖 (override) 波动率表面 (vol surface),不再完全依赖自动计算。
教训:
- 没经历过市场崩盘的定价引擎不可信。Excel 测试覆盖不了边界条件 (edge cases)
- 雪球 Greeks 在障碍价格附近变化剧烈。Delta 一天内可以从 0.2 跳变到 0.8
- 交易台需要一个风险仪表盘 (risk dashboard) 展示每个雪球到障碍价格的距离
技术债积累 (2018 年底)
到 2018 年底:
| 指标 | 数据 |
|---|---|
| 系统组件 | 3 (eds-web-app, hedging-as, edsWeb) |
| 代码库 | 15+ 个 |
| 开发人数 | 8 |
| 总代码行数 | ~30 万行 |
| 数据库表 | 80+ |
| Protobuf 定义 | 50+ |
| ActiveMQ 队列 | 10+ |
痛点:
- 没有测试文化。 几乎没有单元测试 (unit tests)。理由:“我们动得太快了,没时间写测试。”
- 没有 CI/CD。 部署是手动的:
./gradlew build,ssh to server,copy JAR,restart - 没有 API 文档。 Protobuf 定义是唯一的”合约”。想知道某个字段什么意思,要去读
.proto文件 - 没有监控 (monitoring)。 EOD 批处理凌晨 2 点失败,没人知道,直到第二天早上 9 点才发现
- 没有回滚方案。 每次部署都是”祈祷式部署”
但业务在增长。收入比 2016 年翻了 3 倍。系统基本能用。
崩溃点总结:2016–2018 的三个关键时刻
- 雪球业务爆发但定价还在 Excel → 招了量化研究员,建了独立定价引擎
- AccessApp 单体撑不住了 → 三大件拆分,分立了簿记、定价、遗留页面
- 2018 年股灾暴露了 Greeks 不准 → 加了波动率手动覆盖,意识到没经过市场考验的系统不可信
每个节点的应对都是”修一个具体问题”而不是”重构系统”。这个模式会在后续年份反复出现。
下一篇:03-Scaling-2018-2021 —— Vue 前端、监管合规、记录系统