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

Cats Lifecycle

OTC 衍生品 · 19 JUL 2026 · 17 min read · 3,164 words
· · ·

CATS 商品交易业务生命周期 / Commodity Trade Lifecycle


1. 背景:CATS 是什么? / What Is CATS?

CATS(Commodity Auto Trading System)是中金公司 Commodity 部门的商品交易管理系统,覆盖从交易录入到最终结算的全生命周期。它和 ODTS(Options Derivatives Trading System)在同一个技术平台(BaseWeb/Spring+Hibernate)上构建,但 CATS 只处理商品类交易,而 ODTS 处理更广泛的权益类/指数类场外衍生品。

CATS 的核心定位:一个轻量级的一站式商品交易管理平台,不依赖外部交易管理系统,从 Trade Capture 到 Confirmation 到 Settlement 全部自闭环。

业务前提: Commodity 部门的客户主要是产业客户(矿企、贸易商、加工企业)和金融机构,产品以 Commodity Forward/ Swap / Option 为主,标准化程度比 ODTS 的奇异期权要高。


2. Trade Capture:交易怎样进入 CATS?

CATS 的交易录入有两个入口,对应两套完全不同的业务流程。

2.1 自动录入:Bloomberg Feed(TBB / 直通式)

大客户(如矿业公司、对冲基金)的交易通过 BloomBerg TOMS(Trade Order Management System)以电子消息方式推送到 CATS。每条消息是一个 feedRef(Bloomberg 唯一表示)+ feedStatus(操作类型)。

Feed 消息类型(定义见 com.cicc.commodity.statictext.Status):

常量Feed 值含义场景
BB_NEWFT新建新交易的首次录入
BB_AMENDCFT修改交易日起当天修改
BB_CANCELXFT取消交易日起当天取消
BB_P_AMENDPCF交易日之后修改已过 trade date 的修改
BB_P_CANCELPXF交易日之后取消已过 trade date 的取消
BB_CP_CANCELXPF交易日之后取消已过结算日交易最复杂的取消场景

另外还有一套针对 market sector_two=10 的变体(_2 后缀)以及一个独立版本 _3 后缀,说明 Bloomberg 消息格式因交易所/品种而异,CATS 被迫维护了多套兼容映射。——这是业务集成中最棘手的部分:同一个 API 进来,解析逻辑却有 12 种分支。

Feed 处理流程(CommodityBookingService.bookingFromFeed(), commodity/src/.../booking/CommodityBookingService.java):

feed 消息到达
    → CommodityValidationHelper.feedValidate()    // 格式验证
    → CommodityEnrichmentHelper.feedEnrich()       // 数据富化
    → 根据 feedStatus 路由:
        FT (NEW)      → 直接创建新交易
        CFT (AMEND)   → 查 originalFeedRef,新版本 trade.cancelled=false,originalTradeId 指向原版本
        XFT (CANCEL)  → 查 feedRef,原版本 trade.cancelled=true

Enrichment(富化)这一步是关键,它把 Bloomberg 的裸字段转换为业务可用的数据

  1. Counterparty Resolution(对手方识别): Bloomberg 消息中 counterpartyId 可能为空或缩写。CATS 调用 ClientProvider.getBySymbol() 查 RDS(Reference Data Service),判断该对手方是 Client(客户)还是 Broker(经纪商)。空值时使用系统默认对手方(配置文件 KEY_FEED_DEFAULT_COUNTERPARTY)。
  2. Product Resolution(产品识别): 根据 ticker(如 “LMCADP 3”)查 CommodityProductProvider,确认该产品在 RDS 中有注册。如果没有则标记 tradeStatus=PRODUCT_ERROR
  3. Premium Date 计算: 如果是期权,premiumDate = T+1(基于 WeekBusinessCalendar,跳过 weekend/holiday)。
  4. **Legal Entity: ** BookId 决定:COMMDTY2 → FP (CICC FP),其他 → CT (CICC CT)。

Feed 的 tradeStatus 错误标记:

  • FEED_ERROR — 无法识别的 feedStatus
  • CLIENT_ERROR — 对手方查找失败
  • PRODUCT_ERROR — 产品查找失败

这些错误不会阻止交易入库,但会阻止下游流程(确认、结算)执行。

2.2 手动录入:Excel 批量导入

CATS 还有一个不太为人知但大量使用的入口:Excel 批量导入OtcTradeExcelImporter, commodity/src/.../otc/booking/OtcTradeExcelImporter.java)。

这个导入器最初是为 SunGard 系统迁移设计的。它读取 xlsx/csv 文件,解析 sheet 中的行列头映射,批量创建交易。支持的产品类型:

  • Future(期货): fut pos sheet,读取 underlying/tradeDate/volume2/price/dealId
  • Option(期权): 历史代码注释中留有三套模板行头:TRADE_VANILLATRADE_SHARK_FINTRADE_FUTURE,但当前只启用了 Future

此外还有一个 importFundsExcel() 方法,专门导入 SunGard 格式的客户资金数据(22 个资金相关字段 + 前一日余额),直接写入 OtcFunds 表。

2.3 手动录入:UI 直操作

交易员通过 Web UI 录入交易时,走 CommodityBookingService.booking()

Manual trade → validate() → enrich() → 
    if (entityId == null):  // 新交易
        create → 生成 tradeId → 设置 dealId = "bookId-mtardeId"
    else:                   // 修改现有交易
        get original → 原交易 cancelled=true → transType 根据日期路由:
            tradeDate == today  →  BOOKING_CANCEL / BOOKING_AMEND
            tradeDate < today   →  BOOKING_P_CANCEL / BOOKING_P_AMEND
        create amended version → 关联 originalTradeId

这里有一个业务判断: 同一交易日内修改 vs 跨日修改,在会计处理上完全不同(前者可以”修正”原账目,后者必须分开两条记录)。CATS 通过比较 today.compareTo(tradeDate) 来区分。


3. Booking 完成后发生了什么?

每笔交易 createupdate 完成后,CommodityBookingService 最后一行是:

BookingDownstreamJob.enqueue(tradeId);

这条队列启动了一条异步处理链,是 CATS 交易生命周期的核心引擎。

3.1 SystemDownstreamPublishJob

commodity/src/.../downstream/SystemDownstreamPublishJob.java

这是一个 Spring InitializingBean:容器启动 60 秒后,一个后台线程开始消费队列。对于队列中的每个 tradeId:

  1. CommodityService.get(tradeId) — 从数据库拿完整交易数据
  2. JSON.toJSONString(commodity) — 序列化为 JSON
  3. downstreamQueueSender.send(json) — 通过 JMS 发送到下游系统

这个 JMS 消息的消费者是谁? 根据代码日志名为 downstreamLog,这很可能被以下系统消费:

  • Risk system(风险管理系统,用于计算希腊值/Greeks)
  • Accounting system(会计系统,用于记账)
  • Regulatory reporting(监管报送系统)

业务视角: 这是 CATS 与外部系统的唯一单向接口。所有”外部”系统只读 CATS 的 Trade 数据,不能回写。这意味着 ODTS 中那种”Trade →Risk 定价 →Feedback Trade”的闭环在 CATS 中不存在。

3.2 ConfirmationJob

commodity/src/.../confirmation/ConfirmationJob.java

同样是 Spring InitializingBean + 后台队列,但逻辑复杂得多。这个 Job 是**自动确认(Auto-Confirm)**引擎,负责:

  1. Generate(生成 PDF): 调用 ConfirmProducer.produce() 生成确认书 PDF 文件
  2. Send(发送邮件): 调用 EmailService.send(confirm) 发送给客户

Confirmation 逻辑:

dequeue → 拿到 commodities

[DEFAULT_AUTO_CONFIRM_DELAY = 30min]  // 可配置,用于给交易员窗口修改

if (cancelled trade):
    找到原 Confirmation → 如果原确认已发送 → 生成 Cancel Confirmation
                         → 如果原确认未发送 → 标记 CONFIRM_NEW_CANCELLED,跳过
else:
    生成新的 Confirmation PDF

发送邮件:
    if (client == CICCL) → 不发送(2019-03-26 特例处理)
    if (发送成功) → confirm.sendOut=true, 状态更新为 CONFIRM_SENT / CONFIRM_CANCELLED_SENT
    if (发送失败) → 状态 CONFIRM_SEND_ERROR / CONFIRM_CANCELLED_SENT_ERROR

Confirmation Status 状态机(11 种状态):

  NEW
   ├──→ GEN_ERROR (PDF 生成失败)
   ├──→ GENERATED (PDF 生成成功)
   │    ├──→ SEND_ERROR (邮件发送失败)
   │    └──→ SENT (邮件发送成功)
   └──→ NEW_CANCELLED (交易取消时还未生成确认)
   
CANCELLED 时的分支:
   CXL_GENERATED → CXL_SENT / CXL_SENT_ERROR
   CXL_GEN_ERROR
   NEW_CANCELLED (原确认未发送时)

这是一个完备但沉重的状态机。每笔交易在 Confirmation 上有 11 种可能的状态,加上交易本身的 cancelled/deleted/tradeStatus,一笔交易的状态组合超过 40 种。这在运营上意味着:error recovery(错误恢复)不是简单重试,需要人工判断当前处于哪个分支。


4. 资金与结算 / Cash Movement & Settlement

4.1 CashMovementHandler

commodity/src/.../cash/CashMovementHandler.java

CATS 的资金处理是通过每日批处理(EOD)触发。dailyCashMovementByClient() 对每个客户:

  1. ClientTradePnl 表获取当日的 PnL 数据
  2. 按产品类型过滤:Option 的 PnL 不自动生成 CashMovement(因为期权 Premium 和 Settlement 分别在两个不同时间点处理)
  3. 生成 CashMovement 记录,包含:
    • event = PL (PnL settlement)
    • amount = realized PnL
    • currency = USD (统一转换为客户结算货币)
    • receiveDate = pnlDate
    • settled = false
    • genPhase = ST (Statement generation)
    • status = NEW (等待审批)

Approval Workflow: CashMovement 的状态从 NEWAPPROVED 需要人工审批。这是一个业务设计选择:即使系统自动计算 PnL,实际资金调拨必须经风控/运营确认后才执行。

4.2 期权的 Premium 处理

期权有 premiumDate(T+2),处理逻辑独立于普通 PnL:

  • CashMovementHandler.enrichPremium() — 生成 Premium 收付指令
  • 买入期权(Long):支付 Premium
  • 卖出期权(Short):收取 Premium

4.3 Margin Call(追保)

CATS 支持 Margin Call 生成和发送(功能在 Status 中定义了 4 种状态:GEN_ERROR / GENERATED / SEND_ERROR / SENT)。但相比于 ODTS 中复杂的 SIMM/ISDA CSA 计算,CATS 的 Margin Call 处理更原始——它只在 EOD 生成 PDF 通知客户,不涉及 IM/VM 的动态计算。


5. 定价与估值 / Pricing & Valuation

5.1 PricingCalculator

commodity/src/.../otc/pricing/PricingCalculator.java

CATS 的定价引擎是一个内嵌的数学计算库,支持以下产品:

产品类型数学模块用途
Vanilla OptionVanilla普通看涨/看跌期权定价
Call/Put Spread (CPS)CallPutSpread价差期权
Shark FinSharkfin鲨鱼鳍(单鲨)
Double Shark FinDoubleSharkfin双鲨鱼鳍
Enhanced Double Shark FinEDoubleSharkfin增强型双鲨鱼鳍
Barrier OptionBarriers障碍期权

Pricing Calculator 的工作流:

  1. 取 underlying 数据: OtcUnderlyService.getUnderlyByTickerAndRefDate() — 从数据库取指定 ticker 在估值日的结算价
  2. 取产品元数据: UnderlyingProvider.getByProductSymbol() — 查 RDS 产品信息
  3. 计算 Greeks: 每个产品类型计算 delta/gamma/vega/theta
  4. Margin 计算: 根据 margin rate markup、participation rate 等参数计算保证金
  5. 输出 OtcPnlOption: 包含 mtm、delta、gamma、vega、theta、margin 等

关键发现:CATS 的定价引擎是硬编码的,没有 ODTS 中的 Pricing Strategy Pattern。 每种产品类型的定价逻辑直接在 PricingCalculator 中通过 ContractType switch 路由。这意味着新增产品类型需要修改核心定价类,不符合开闭原则(Open/Closed Principle)。

5.2 估值频率

定价是 EOD 触发的批处理过程。每日收盘后:

  1. 拉取所有 market data(交易所结算价、Bloomberg 行情)
  2. 遍历所有未到期交易的 Portfolio
  3. 逐个调用 PricingCalculator 计算 MTM 和 Greeks
  4. 结果写回 OtcPnlOption / OtcPnlVanilla
  5. 汇总到 Client/Book 级别 PnL 用于 CashMovement

没有支持日内实时的 on-demand 定价——这是 CATS 与 ODTS 的一个重要差异(ODTS 有独立的 Pricing Engine 支持实时询价)。


6. EOD 批处理流程 / End-of-Day Batch

CATS 的 EOD Batch 大致按以下顺序执行:

时间轴 (T日收盘后):
  17:00 — Batch 调度启动 (Quartz Scheduler)

  1. 定价 Engine 运行:所有未到期 Option + Future 的 MTM 计算

  2. PnL 归因:每个 Book/Client 的 realized + unrealized PnL

  3. CashMovement 生成:Non-Option 的 PnL → CashMovement (NEW)

  4. Premium 处理:期权 Premium 收付指令

  5. Statement 生成:客户对账单 PDF

  6. Margin Call 计算:根据持仓计算保证金需求

  7. 下游分发:SystemDownstreamPublishJob 推送数据到 Risk/Accounting
  次日 09:00

批处理中有两个”窗口”值得注意:

  • Confirmation Delay(默认 30 分钟): 交易在入录后 30 分钟才进入确认流程。这是给交易员一个”后悔期”(cooling-off period),在此期间可以修改或取消交易而不触发确认书发送。
  • CICCL 白名单例外(2019-03-26): CICC London 客户的确认书不自动发送邮件,而是走另一套流程。这是业务特例,直接写死在了 ConfirmationJob 中。

7. CATS 中的状态管理 / State Machine

CATS 的 Trade 在数据库层面使用字段组合来表示业务状态(而非单一枚举):

// Commodity (extends Trade) 的关键状态字段
boolean cancelled;           // true = 已取消
boolean deleted;             // true = 已删除(逻辑删除)
String confirmationStatus;   // 11种确认状态
String tradeStatus;          // APPROVED / FEED_ERROR / CLIENT_ERROR / PRODUCT_ERROR
String transType;            // NEW / AMEND / CANCEL / P_AMEND / P_CANCEL
String feedStatus;           // FT / CFT / XFT (Bloomberg 原始类型)

没有单一的状态字段表示”这笔交易现在是什么状态”。 业务状态是上述字段的组合推断。例如:

  • “正常活动的交易”:cancelled=false && deleted=false && tradeStatus=APPROVED
  • “已取消但还未生成确认书的交易”:cancelled=true && confirmationStatus=NEW_CANCELLED
  • “Feed 进来的取消指令但原交易不存在”:cancelled=false && tradeStatus=FEED_ERROR

这种设计在读取时需要组合判断,SQL 查询中需要多个 Restrictions 组合(如 CommodityService.listPendingTrades() 中复杂的 Criterion AND/OR 组合)。从业务角度看,状态的可理解性交给了 DAO 层的查询逻辑,而不是模型层。


8. 产品覆盖 / Product Coverage

CATS 支持的主要产品类型:

ContractType内部覆盖度说明
COMMODITY FORWARD完整最基础的商品远期
OPTION (Vanilla)完整普通看涨/看跌期权
CALL_PUT_SPREAD完整价差组合
FUTURE完整(含 SunGard 迁移导入)期货
SHARKFIN完整(需数学计算)鲨鱼鳍结构
DOUBLE_SHARKFIN完整双鲨鱼鳍
BARRIER基础障碍期权
FX基础(不生成确认书)外汇远期/掉期

空白处: CATS 没有 Snowball(雪球)产品。在 ODTS 中是核心产品的 Snowball,在 CATS 中不存在——因为 Snowball 在 2018 年后才兴起,而 CATS 的开发周期主要在 2014-2018 年。


9. CATS vs ODTS:生命周期差异

维度CATSODTS
Trade CaptureBloomberg Feed + UI + ExcelUI + ETF/Stock Feed
Confirmation自动生成 PDF + 自动发邮件(11 种状态)自动生成 + 手动触发发送
Pricing Engine内嵌硬编码(Vanilla/CPS/SharkFin)Pricing Strategy Pattern,独立 Engine
Real-time Pricing不支持支持实时询价
Cash Movement每日 EOD 统一处理,NEW→APPROVED实时 + EOD
Margin CallPDF 通知(4 种状态)SIMM + CSA 计算,复杂 Collateral Management
Settlement手动审批批量 + 手动
DownstreamJMS JSON 推送(单向)JMS + 直接 DB 访问(双向)
技术债务多套 feedStatus 映射、硬编码特例同样有大量特例,但有微服务边界

10. 基于代码的证据 / Evidence from the Codebase

这篇文档中的所有判断基于以下 CATS 源码文件的模式分析:

  • CommodityBookingService.java — 交易录入核心,三种分支(new/amend/cancel)
  • CommodityEnrichmentHelper.java — 对手方/产品/日期富化
  • OtcVanValidator.java, OtcCpsValidator.java — 合约验证
  • SystemDownstreamPublishJob.java — JMS 下游分发
  • ConfirmationJob.java — 自动确认 + 邮件发送
  • PricingCalculator.java — 定价引擎(调用 Vanilla/CPS/SharkFin 数学模块)
  • CashMovementHandler.java — 资金处理 + 审批工作流
  • OtcTradeExcelImporter.java — Excel/CSV 批量导入
  • Status.java — 所有状态常量的唯一出处
  • Trade.java, Commodity.java — 数据模型定义

10.5 业务代价:CATS 的设计选择意味着什么成本?

40+ 种状态组合 = 运营排查黑洞

第 3.2 节指出,一笔交易的状态是 cancelled / deleted / confirmationStatus(11) / tradeStatus / transType / feedStatus字段组合,组合超过 40 种。从运营视角:

  → 一笔"卡住"的交易,人工要逐字段判断它到底卡在哪个分支
  → error recovery 不是"重试按钮",而是"读懂状态机后手工改字段"
  → 新人培训成本高:理解这 40 种组合需要读源码,而非看文档

PM/BA 启示: 状态机写在代码里(第 7 节)、不配置化,意味着每一次状态逻辑的变更都要改 Java + 发版,而不能靠运营配置解决。这在”监管临时要求新增一种交易状态”时尤其 painful。

Monolith 的一切都在一个部署单元 = 扩展性天花板

第 11 节总结提到,CATS 是”一个 monolith 内部把所有事情做了”——从 Bloomberg feed 解析到 PDF 生成到邮件发送。代价是:

  → 新增产品类型(如想加 Snowball)要改核心 PricingCalculator(第 5.1 节已指出它硬编码)
  → 新增外部接入要改 DownstreamJob
  → 特例(CICCL 不发确认书)hardcode 在业务代码里

业务影响(估算):
  → 一个"加新品种"的需求,在 CATS 要动核心类 + 回归全部 11 种确认状态 + 重测 EOD 链路
  → 这比 ODTS 的微服务边界(加品种只动 Pricing Engine 一个服务)慢得多
  → 2014-2018 年开发周期里没做 Snowball,等到 2018 后雪球爆发时,
    CATS 已定型,只能靠 ODTS 扛雪球(见 34 文档)——两套系统边界的代价在此显现

Feed 的 12 种解析分支 = 隐性维护负债

第 2.1 节提到同一 Bloomberg API 进来,解析逻辑有 12 种分支(含 _2 / _3 后缀变体)。这意味着:

  → 每次 Bloomberg 改一次消息格式,CATS 要逐一核对 12 个分支是否还成立
  → 这是 50 文档讲"双系统客户端暴露合并缺口"的同源问题:
    外部格式变体越多,内部兼容代码越脆弱
  → 维护成本随"外部系统版本数"线性增长,而非随"产品数"增长

11. 总结 / Summary

CATS 是一个务实的、面向 Commodity 业务的一站式交易管理系统。它的设计决策反映了 Commodity 业务的特点:产品类型相对固定、客户以产业客户为主、交易量相对小但单笔金额大、业务流程可以接受隔夜批处理而不是实时响应

与 ODTS 相比,CATS 的系统边界更窄(只覆盖 Commodity),但生命周期覆盖更完整(从 feed 到 settlement 完全自闭环)。ODTS 依靠多个微服务和外部 Pricing Engine 协作,CATS 则是一个 monolith 内部把所有事情做了——从 Bloomberg feed 解析,到 PDF 确认书生成,到邮件发送,到定价计算,到资金处理。

这种”everything in one place”的设计简化了运维(只有一个部署单元),但代价是扩展性受限:新增产品类型需要改核心类,新增外部系统接入需要修改 DownstreamJob,特例逻辑(如 CICCL 不发确认书)直接 hardcode 在业务代码中而不是配置化。