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

How To Read The Code

OTC 衍生品 · 19 JUL 2026 · 13 min read · 2,412 words
· · ·

ODTS 10 — 怎么看代码:开发者实用指南

为什么需要这篇文档

前面 9 篇文档告诉你了系统是什么。这篇告诉你怎么改它

每一节从一个开发者场景开始:“我要做 X。“这一节告诉你准确在哪里看、读什么、改什么。

前置条件: 你读过 01–09。你了解 Java、Spring Boot、Vue 2 基础。你知道 TRS 是什么。


场景 1:我要给合约加一个字段

业务: 交易台要在每笔 TRS 交易上记录一个新属性——比如”融资重置频率 (funding reset frequency)”。

为什么业务需要这个字段: 融资利率从”每月重置”改为”每季度重置”意味着客户的融资成本更稳定(不随 SHIBOR 月波动剧烈变化)。这个改动来自几个大客户的要求——他们认为月度重置增加了不确定性。如果字段加错了或漏加了,TRS 的利息计算全部按默认频率走,客户对账单的融资费用就不对。

找到领域模型

合约领域模型在 hedging-as(定价团队定义了模型):

hedging-as/src/com/cicc/service/contract/model/

找与你业务对象匹配的类。对 TRS,ContractLegStruct.java 定义了 leg 属性。合约头看 ContractHelper.java

改动的链条

hedging-as/src/com/cicc/service/contract/model/ContractLegStruct.java
    ↓ (如果用 protobuf 则重新生成)
hedging-as/src/com/framework/protobuf/*/  (如果字段跨服务边界)

eds-web-app/db/migration/Vxxx__add_funding_reset_freq.sql

eds-web-app/src/com/cicc/edsBoot/mapper/ContractMapper.xml

eds-web-app/src/com/cicc/edsBoot/controller/ContractController.java

new-edsweb/src/views/option-contract/ContractForm.vue

场景 2:交易价格算错了

业务: 客户报告他们的雪球 MTM 值不对。

第 1 步:理解客户看到的价格

找估值记录:

SELECT * FROM TodayContractRaInfo
WHERE contractId = 'SNOWBALL20250716001'
ORDER BY tradeDate DESC LIMIT 1;

这告诉你系统算出的 MTM、delta、gamma、vega。

第 2 步:检查行情数据

大部分定价 bug 是行情数据问题,不是公式问题:

hedging-as/src/com/cicc/pricing/dataCenter/
├── PriceCache.java         — 标的价格对吗?
├── VolSurfaceManager.java  — Vol 曲面是最新的吗?
└── IRCurveManager.java     — 利率对吗?

常见问题:

  • 标的价格是 T-1 的数据(行情数据源断过)
  • 波动率曲面没更新(曲面构建器用了过期数据)
  • 股息预测缺失(dividend helper 跳过了某只股票)
  • 美元计价交易的汇率不对

第 3 步:手动重跑估值

你可以对单笔合约触发手动估值:

  1. ValuationAction.startValuation()hedging-as/src/com/cicc/action/valuation/
  2. 或者用 eds-web-app 的 EOD debug endpoint

第 4 步:检查 Payoff

如果行情数据正确,问题在收益函数:

hedging-as/src/com/cicc/edslib/payoff/SnowballPayoff.java

常见 bug:

  • 敲出障碍比较方向(>= vs >
  • 票息计息的日计数惯例 (day count convention):30/360 vs ACT/365
  • 记忆雪球 (Memory Snowball) 逻辑:未付票息没有累积

场景 3:EOD 批处理失败了

业务: 今天的日终处理没完成。仓位和保证金都还是昨天的。

EOD 失败时业务在做什么: 交易员和运营在等,但也得继续工作。2022 年一次 EOD 延迟到晚上 22:00——期间运营手动计算了 TOP 10 头寸的 MTM 发给交易员(用 Excel),交易员用这些手工 MTM 做当晚的保证金催缴估算。这种”手工 EOD”发生过至少 5 次。每次需要 2 人花 2-3 小时,而且手工计算的出错率比系统高——有一次估算和第二天系统实际结果差了 5%。

EOD 在哪里跑

hedging-as/src/com/cicc/action/eod/       — EOD 编排
hedging-as/src/com/cicc/service/eod/      — EOD 步骤实现

EOD 批处理顺序

EOD 批处理是一系列顺序步骤。每一步是 EodScheduler 中的一个方法或一个独立定时任务:

 1. MarketDataRefresh  — 获取最新价格、vol、利率
 2. Fixing             — 处理浮动 leg 的定盘事件
 3. CorporateAction    — 应用分红、拆股、并购
 4. BarrierCheck       — 检查雪球和障碍期权的 KO/KI
 5. Valuation          — 对所有活跃合约重新估值
 6. Greeks             — 计算 delta、gamma、vega、theta、rho
 7. Margin             — 计算 IM + VM
 8. CashMovement       — 生成付款指令
 9. Reporting          — 中期协报告、客户报告
10. Cleanup            — 归档临时数据、更新状态

如何诊断失败

  1. 查日志: EOD 日志去 hedging-as/logs/eod.log。每一步记录开始和结束时间。

  2. 找哪一步失败了:

    grep "EOD_STEP" hedging-as/logs/eod.log | tail -20

    失败前最后完成的那一步就是嫌疑步骤。

  3. 常见失败:

    • 行情数据: 一个或多个标的没有价格 → 第 1 步失败
    • 公司行为冲突: 公司行为条款意外 → 第 3 步失败
    • 超时: 一个复杂雪球估值跑太久 → 第 5 步超时
    • 空指针 (Null pointer): 合约缺少数据(如没有 leg 定义)→ 任何步骤
  4. 从失败步骤重新执行: 每一步 EOD 都是幂等的 (idempotent)。你可以单独调用相应步骤的 action 从失败点重跑。


场景 4:我要加一个新品种

业务: 交易台要做 Variance Swap。这是一个系统里完全没有的新品种。

需要加什么

组件位置工作量
Payoff 函数hedging-as/edslib/payoff/VarianceSwapPayoff.java3–5 天
估值方法hedging-as/edslib/valuation/1–2 天
品种类型枚举eds-web-app/com/cicc/constant/1 小时
定价服务hedging-as/pricing/service/VarianceSwapPricingService.java1–2 天
簿记表单new-edsweb/src/views/variance-swap/2–3 天
API Controllereds-web-app/edsBoot/controller/VarianceSwapController.java1 天
确认书模板eds-web-app/template/variance_swap.docx1–2 天
EOD 处理hedging-as/service/eod/VarianceSwapEodStep.java1–2 天
保证金规则hedging-as/pricing/margin/VarianceSwapMargin.java1–2 天
中期协报告eds-web-app/action/sacReport/VarianceSwapReportStep.java1 天
测试以上所有3–5 天

总计: 一个人 3–4 周,两个人 2 周。

Payoff 实现模式

public class VarianceSwapPayoff {
    public double computePayoff(double[] prices, double strikeVar, double notional) {
        int n = prices.length - 1;
        double sumSquaredLogReturns = 0;
        for (int i = 1; i < prices.length; i++) {
            double logReturn = Math.log(prices[i] / prices[i-1]);
            sumSquaredLogReturns += logReturn * logReturn;
        }
        double realizedVar = sumSquaredLogReturns / n * 252;
        return (realizedVar - strikeVar) * notional / (2 * strikeVar);
    }
}

场景 5:UI 页面加载不出来

业务: 用户点击”合约簿记”,看到白屏。

第 1 步:打开浏览器控制台 (F12)

  • 红色的 Vue 报错: JavaScript 异常——可能是缺少 import 或 null 引用
  • Network tab → 4xx/5xx: API 调用失败
  • Network tab → 200 但没数据: API 返回空结果

第 2 步:追踪请求

浏览器 URL: /eds-boot/views/contract/list.html


Nginx → 从 new-edsweb/dist/ 返回 Vue SPA HTML


Vue 加载 → 调用 API: GET /eds-boot/api/v1/contract/list


Spring Boot (eds-web-app) → 读 DB → 返回 JSON


Vue 渲染表格

需要排查的地方:

  1. new-edsweb/src/views/option-contract/ContractList.vue — 组件对吗?
  2. new-edsweb/src/api/option-contract/contractApi.js — API URL 对吗?
  3. eds-web-app/.../ContractController.java — 端点工作正常吗?
  4. Nginx 日志 — 路由对吗?

常见 Vue 加载 bug

症状可能原因修复
白屏,控制台有 [Vue warn]缺少组件 importimport X from './X.vue'
白屏,无控制台错误Vue Router 配置错误检查 src/router/index.js
API 返回 404Controller 路径或方法不对检查 @RequestMapping
API 返回 500后端异常eds-web-app/logs/
Element UI 样式破掉缺少 CSS import检查 main.js 的 import

场景 6:要调试一条 Protobuf 消息

ODTS 用 Protobuf 做服务间通信。消息定义在 .proto 文件中,编译成 Java。

.proto 文件在哪

hedging-as/src/com/framework/protobuf/
├── bookingAs/         — 簿记服务消息
├── gateway/           — 网关协议
├── hedgingAs/         — hedging-as 特有消息
├── pricingAS/         — 定价服务消息
├── reckoning/         — 结算消息
├── trade/             — 交易数据消息
└── util/              — 共享类型(日期、金额等)

怎么追踪一条 Protobuf 消息

  1. 找到消息的 .proto 定义
  2. 找到创建消息的 Java build() 调用(搜索 .newBuilder()
  3. 找到对面读取消息的 parseFrom() 调用
  4. 检查传输层:ActiveMQ 队列名或 Dipper endpoint

调试技巧

log4j2.xml 中启用 Protobuf 日志:

<Logger name="com.framework.protobuf" level="TRACE"/>

这会记录每条序列化/反序列化的消息,包括十六进制 dump。


场景 7:数据库查询慢

业务: 合约列表页面加载需要 30 秒。

查询路径

  1. Chrome DevTools → Network tab → 看哪个 API 慢
  2. 这个 API 调用 ContractController.list() → 调 ContractDao → 执行 SQL

找到 SQL

MyBatis mapper 在:

eds-web-app/src/com/cicc/edsBoot/mapper/

DAO 实现:

eds-web-app/src/com/cicc/edsBoot/dao/

常见性能问题

问题症状修复
缺少索引CtrContract 全表扫描在 WHERE 列上加索引
N+1 查询合约列表 → N 次 leg 查询用 JOIN 或批量查询
结果集太大一次加载 10000 条合约加分页 (大部分视图已经做了)
没有缓存相同 SQL 跑了 100 次加 Redis 缓存 (RedisOperationController)

场景 8:我搞崩了生产环境,救命!

应急手册

  1. 冷静。 大部分 bug 不是灾难性的。这个系统已经跑了 10 年。

  2. 前端还是后端?

    • 打开浏览器控制台
    • 页面能加载但数据不对 → 后端
    • 页面加载不出来 → 前端
    • 看到 500 错误 → 后端
    • 看到 404 → 路由问题(前端)或端点不存在(后端)
  3. 查日志:

    • hedging-as/logs/ — 定价、EOD、保证金日志
    • eds-web-app/logs/ — API、簿记、确认日志
    • new-edsweb/logs/ — 前端
  4. 回滚策略:

    • UI bug: 回退 Vue 构建产物,重新部署 new-edsweb/dist/
    • API bug: 回退 Spring Boot JAR,运行 copy_restart_jar.sh
    • 定价 bug: 回退 hedging-as JAR
    • 数据库 schema 变更: 运行回滚 migration
    • 配置: 从 git 历史恢复之前的版本
  5. 告诉业务:

    • “我们发现了一个数据问题”(如果是行情数据)
    • “我们发现了一个计算 bug 并已部署修复”(如果是代码问题)
    • “我们正在用修正后的数据重新运行 EOD 批处理”(如果是批处理问题)

场景 8.5:我要查一笔交易”为什么今天没被估值”

业务: 交易员问,“我昨天簿记的雪球,今天的 MTM 日报里怎么没有?”

这不是”价格算错”(场景 2),而是”这笔交易压根没进估值范围”。这类问题的根因几乎都不在定价引擎,而在”合约有没有被正确纳入 EOD 的活跃合约集合”。

排查路径(按命中率排序)

  1. 合约状态对不对。 CtrContract.status 必须是 BOOKEDCONFIRMED 之类的”活跃”值。如果它卡在过渡态(见 05 的幽灵状态问题),ValuationAction 的预处理会直接跳过它。

  2. 有没有被 EOD 的”排除名单”捞走。 hedging-as 的 EOD 编排里有一段”特殊合约过滤”逻辑——某些处于争议、司法冻结、或手工锁定的合约会被加到一个排除集合,不参与自动估值。这段逻辑在 service/eod/EodContractFilter.java,经常被人忘记。

  3. 定盘/行情绑定对不对。 如果这笔交易关联的标的价格在 MarketDataRefresh 第 1 步就没拿到(断行情),BarrierCheckValuation 都会因为缺数据而跳过或报错——表现就是”整批里就它没出数”。

-- 快速确认:这笔交易在今天的估值表里有记录吗?
SELECT contractId, tradeDate, mtm, status
FROM TodayContractRaInfo
WHERE contractId = 'YOUR_CONTRACT_ID';
-- 如果没行 → 它没进估值范围(状态/过滤/行情问题)
-- 如果有行但 mtm 为 NULL → 估值跑了一半因缺数据失败

内行提示: 新人常一头扎进 SnowballPayoff 去改公式,折腾半天发现交易根本没被估值。记住 ODTS 的一个分层铁律——“有没有被算”和”算得对不对”是两个独立的问题,前者先查状态/过滤/行情,后者才查 Payoff


改代码的真实代价:一条字段链牵动五个系统

场景 1 里”给 TRS 加一个字段”的改动链(hedging-as 模型 → protobuf → migration → mapper → controller → 前端表单),表面是 6 个文件,但业务风险不在文件数,而在”漏改”

漏了 migration   → 字段没进 DB,运行时报列不存在,交易簿记直接失败
漏了 protobuf    → 跨服务字段传丢,hedging-as 拿到默认值,MTM 算错
漏了 mapper       → SQL 不认新列,查询静默返回旧值(最危险:不报错)
漏了 controller   → 前端提交了但后端不接收,数据"消失"
漏了 前端表单      → 交易员根本没地方填这个字段

PM/BA 启示:
  这类"跨 5 层加字段"的需求,测试成本 ≈ 改代码成本,
  因为它要在每一层都验证"新字段真的端到端通了"。
  一次漏改如果进了生产,后果是真实的资金数据错误
  (呼应 34 文档:错误的日终/估值数据 = 真实的资金风险,不是 UI bug)。
  所以看起来"加一个字段"的小需求,在 OTC 衍生品系统里
  永远不该被估成"小"——它的爆炸半径横跨五个系统。

开发工作流总结

┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
│ 1. 读     │    │ 2. 写     │    │ 3. 构建   │    │ 4. 部署   │
│ 现有代码  │───▶│ 改代码    │───▶│ 本地测试  │───▶│ 到 beta   │
│          │    │          │    │          │    │ → prd    │
└──────────┘    └──────────┘    └──────────┘    └──────────┘

读 — 在代码库里 grep 你在做的业务术语。“knockout” 会找到 ContractKnockInOut.java。“margin” 会找到 MarginCalculator.java。ODTS 类名很有描述性。

写 — 遵循现有模式。如果代码库用 ContractHelper 做构造逻辑,把改动放在那里,而不是另写一个工具类。

构建 — 每个项目都有 Gradle wrapper:

eds-web-app: ./gradlew build
hedging-as:  ./gradlew build
new-edsweb:  npm run build
odts-option-web: npm run build

部署 — 看 eds-web-app 里的 copy_restart_jar.sh,如果有 CI/CD 管线就用管线。


自测

  • 发现 EOD 在第 5 步失败了,你怎么从第 5 步重新执行?
  • 合约字段改了之后,需要改哪 5 个地方?(从 DB 到 UI)
  • 价格不对,排查顺序是什么?
  • 一个新品种上线需要改几个模块?估算时间。

现在带着这些知识回去重读 01-Origins-2014-2016.md,你会看懂的。