4. Strata 核心架构
OpenGamma Strata 是一个面向利率衍生品定价与风险计算的开源 Java 库。它的核心价值不在于提供某个单一算法,而在于用一套清晰、可组合的架构,把市场数据、交易模型、定价函数与计算引擎解耦开来。理解这套架构,是后续进行风险实战(如曲线构建、敏感度计算、场景分析)的前提。本节从模块划分、核心接口、市场数据模型、交易模型与计算引擎五个维度,梳理 Strata 的总体设计。
一、模块划分:按职责分层,而非按产品堆叠
Strata 的 Maven 模块划分体现了“基础能力下沉、业务能力上浮”的原则。核心模块大致可分为四层:
- 基础层(Basics):提供日期、货币、天数计算、日历、曲线节点等通用类型,如
LocalDate扩展、DayCount、HolidayCalendar。这一层不依赖任何金融业务概念。 - 市场数据层(MarketData):定义
MarketData、Curve、Surface、MarketDataId等接口,负责“数据是什么”以及“如何被引用”。 - 产品与交易层(Product / Trade):定义
Product、Trade、Security等接口,描述“交易是什么”。 - 定价与计算层(Pricing / Calculation):包含
CalculationFunction、CalculationRunner、PricingRule等,负责“如何算”以及“如何批量算”。
这种分层带来的直接好处是:新增一个产品时,只需实现产品接口和对应的计算函数,而不必改动市场数据或引擎代码。风险实战中常见的“自定义曲线”“自定义定价规则”正是通过扩展这些接口实现的。
二、核心接口:四类契约支撑整个计算链路
Strata 的扩展点集中在少数几个接口上,理解它们就掌握了架构的“关节”:
| 接口 | 职责 | 典型实现/使用场景 |
|---|---|---|
MarketDataId<T> | 唯一标识一项市场数据 | 曲线 ID、波动率面 ID |
MarketData | 提供按 ID 查找数据的能力 | 场景数据、快照数据 |
CalculationFunction<T> | 对某类目标执行计算 | 定价、敏感度、现金流 |
CalculationRunner | 批量调度计算任务 | 多交易、多场景并行计算 |
其中 CalculationFunction 是最关键的扩展点。它接收一个 CalculationTarget(通常是交易或产品),返回一组 CalculationResult。风险指标(如 PV、Delta、Gamma)本质上都是通过不同的 CalculationFunction 实现的。
三、市场数据模型:ID 与数据的分离
Strata 市场数据模型的核心思想是“引用与内容分离”。交易本身不持有曲线对象,而是持有 MarketDataId;计算时由 MarketData 容器根据 ID 解析出实际数据。这样做的好处是:同一笔交易可以在不同场景下复用,只需替换 MarketData 容器即可。
关键设计:曲线不是“一个对象”,而是“一组带标签的节点 + 插值器 + 日期偏移”。Curve 接口通过 yValue() 或 value() 暴露查询能力,而 CurveMetadata 则记录曲线名称、货币、天数计算等元信息。风险计算中常用的“曲线 bump”正是通过包装原始曲线并修改节点值来实现的。
常见的市场数据 ID 包括 CurveId、SurfaceId、FxRateId 等。在场景分析中,我们通常会构建一个 MarketData 的映射,把 ID 映射到具体的曲线或价格,然后交给计算引擎。
四、交易模型:产品、交易与证券的三角关系
Strata 用三个概念描述“一笔业务”:
- Product:金融产品的数学描述,如
Swap、Fra、Swaption。它不包含交易对手、日期等上下文。 - Trade:产品 + 交易信息(交易日、对手方、数量、方向)。
- Security:可交易的标准化证券,通常与
Trade组合使用。
这种拆分让定价函数可以只针对 Product 编写,而风险系统则围绕 Trade 组织。例如,计算一笔互换的 PV 时,CalculationFunction 会从 Trade 中提取 Product,再结合 MarketData 完成计算。
五、计算引擎:从单笔定价到批量风险
Strata 的计算引擎围绕 CalculationRunner 构建。它的典型工作流程如下:
- 定义计算目标(一组
Trade或Product)。 - 定义计算任务(如
PV、Delta、CashFlows)。 - 准备
MarketData容器和CalculationRules(参考数据、定价规则)。 - 调用
CalculationRunner执行,得到Results。
在风险实战中,我们通常会对同一组交易执行多次计算,每次替换不同的 MarketData(即场景),从而得到 PV 分布或敏感度。Strata 的 ScenarioMarketData 和 CalculationRunner 的组合正是为此设计的。
// 伪代码示意:构建计算任务并执行
CalculationRules rules = CalculationRules.of(...);
MarketData marketData = ...;
List<Trade> trades = ...;
Set<CalculationTask> tasks = CalculationTask.of(trades, PV, marketData, rules);
Results results = CalculationRunner.of(executor).calculate(tasks);
实战提示:在利率风险场景中,曲线 bump 通常通过 ScenarioMarketData 实现。每个场景对应一组被扰动的曲线,引擎会并行计算所有场景下的 PV,最终汇总为 Delta、Gamma 等指标。理解 MarketDataId 与 CalculationFunction 的配合,是排查“为什么某个风险指标算不出来”的关键。
六、常见误区与复盘
误区一:把曲线对象直接塞进交易。Strata 的设计要求交易只持有 ID,数据由外部容器提供。如果违反这一点,场景分析将无法复用交易对象。
误区二:混淆 Product 与 Trade。定价函数通常针对 Product 编写,而风险汇总针对 Trade。混用会导致计算目标不明确。
误区三:忽略 CalculationRules。参考数据(如日历、天数计算)和定价规则(如折现曲线选择)都通过 CalculationRules 注入。忽略它会导致结果与预期不符。
复盘来看,Strata 架构的精髓在于用接口隔离变化:市场数据变化不影响产品定义,产品变化不影响计算引擎,计算引擎变化不影响风险汇总。掌握这四层接口与两类模型(市场数据模型、交易模型),就掌握了 Strata 风险实战的底层骨架。