基于流引擎的 FICC 实时曲线(曲面)构建与定价实践
在固定收益、外汇和大宗商品(FICC)领域,实时构建市场数据(收益率曲线/波动率曲面等)并对资产进行定价是量化交易和风险管理的核心任务。DolphinDB 提供了面向 FICC 场景的市场数据引擎(Market Data Engine)与定价引擎(Pricing Engine)。市场数据引擎订阅各类实时行情数据,实时构建债券收益率曲线(Bond Yield Curve)、单货币利率互换收益率曲线(Ir Single Currency Curve)、跨币种利率互换收益率曲线(Ir Cross Currency Curve)、外汇波动率曲面(Fx Volatility Surface)等定价需要用到的市场数据。定价引擎接收市场数据引擎输出的各类最新市场数据,对预设的金融工具(债券、利率互换、外汇远期、外汇期权等)进行实时定价并输出定价结果。从历史数据库加载债券、利率互换、外汇即期、外汇掉期、期权波动率等行情数据回放,模拟实时场景,驱动市场数据引擎生成最新市场数据、定价引擎实时定价。
1. 总体架构
本教程提供债券、利率互换、外汇三个典型的 FICC 业务场景,讲解如何基于双引擎搭建完整的 FICC 实时市场数据构建与定价系统。为避免重复叙述,教程先在第 2 章抽取通用的核心概念与标准化流程,随后在各场景中分别提供可直接运行的完整实例脚本。
系统总体架构如图 1-1 所示:
整个系统的核心链路为:
通过本教程三个场景的实践,读者可以实现:
-
多类行情数据(债券到期收益率、存款利率、利率互换报价、外汇即期、外汇掉期点、外汇期权波动率报价等)的实时回放与接入;
-
实时构建收益率曲线、跨币种曲线、波动率曲面等各类市场数据;
-
对债券、利率互换、外汇远期、外汇期权等多种金融工具进行实时定价,并支持 Greeks 风险指标输出。
2. 核心概念与通用流程
上述三个业务场景均基于统一的引擎架构与开发范式。因此,本章将集中阐述双引擎的核心概念与通用开发流程,第 3~5 章则聚焦于各资产场景的差异化配置与实战实现。本教程全部代码需要运行在 3.00.6 或者更高版本的 DolphinDB server 上。
2.1 通用处理流程
无论哪个业务场景,搭建“行情 → 曲线/曲面 → 定价”实时链路的步骤都遵循同一个六步范式:
-
环境清理:取消旧回放任务,删除旧引擎、流表和订阅,保证脚本可反复运行。关键函数:
cancelJob、dropStreamEngine、dropStreamTable、unsubscribeTable。// ---- 0. 清理旧任务、订阅、流表和引擎,支持重复运行 ---- try { cancelJob("replayQuotesJob") } catch(ex) {} try { dropStreamEngine("FICC_MKT_ENGINE") } catch(ex) {} try { dropStreamEngine("FICC_PRICING_ENGINE") } catch(ex) {} try { dropStreamTable("pricingResult") } catch(ex) {} try { dropStreamTable("curveResult") } catch(ex) {} try { unsubscribeTable(tableName="marketQuotes", actionName="AppendQuotesToMktDataEngine") } catch(ex) {} try { dropStreamTable("marketQuotes") } catch(ex) {} -
创建流表:创建行情流表 marketQuotes、定价结果流表 pricingResult、曲线结果流表 curveResult。关键函数:
share streamTable。// ---- 1. 创建需要的流表 ---- // a.行情流表(用于接收模拟行情数据,供市场数据引擎订阅) share streamTable(100000:0, `timestamp`type`name`price, [NANOTIMESTAMP, STRING, STRING, DOUBLE]) as marketQuotes // b.定价结果流表,用于接收定价引擎输出的定价结果 share streamTable(1:0, `name`date`npv`eventTime`sysTime`delayStart, [STRING, DATE, DOUBLE, NANOTIMESTAMP, NANOTIMESTAMP, NANOTIMESTAMP]) as pricingResult // c.曲线/曲面结果流表,用于接收市场引擎输出的收益率曲线数据 share streamTable(1:0, `eventTime`kind`date`name`data`sysTime, [NANOTIMESTAMP, STRING, DATE, STRING, MKTDATA, NANOTIMESTAMP]) as curveResult -
构建金融工具:将待定价工具基础信息解析为 INSTRUMENT 对象,并创建定价引擎。关键函数:
parseInstrument、createPricingEngine。在实际运行环境中,INSTRUMENTS 通常来自于数据库表。// 通过 parseInstrument 构建 INSTRUMENT 对象 instruments = parseInstrument(instTb) // 创建定价引擎 pricingEngine = createPricingEngine("FICC_PRICING_ENGINE", instruments, pricingHandler, priceEngCfg) -
配置市场数据:编写曲线/曲面构建配置,创建市场数据引擎。关键函数:
createMktDataEngine。// 曲线/曲面构建配置 curveCfg = { "name": "CNY_TREASURY_BOND", "type": "BondYieldCurve", ... "currency": "CNY", "dayCountConvention": "Actual365" } // 创建市场数据引擎(模式一、引擎级联模式,级联定价引擎pricingEngine) // mktEngine = createMktDataEngine(name="FICC_MKT_ENGINE", referenceDate=referenceDate, mktDataConfig=[curveCfg], handler=pricingEngine, engineConfig=mktEngCfg) // 创建市场数据引擎(模式二、非级联模式,通过 mktDataHandler appendMktData 市场数据到定价引擎pricingEngine) mktEngine = createMktDataEngine(name="FICC_MKT_ENGINE", referenceDate=referenceDate, mktDataConfig=[curveCfg], handler=mktDataHandler{ pricingEngine }, engineConfig=mktEngCfg) - 订阅行情:将行情流表数据推送进市场数据引擎。关键函数:
subscribeTable。// 订阅行情流表,通过行情数据驱动市场数据引擎构建曲线和曲面、驱动定价引擎定价 subscribeTable(tableName="marketQuotes", actionName="AppendQuotesToMktDataEngine", offset=-1, handler=append!{mktEngine}, msgAsTable=true) - 行情驱动:模拟或接入真实逐笔行情,驱动曲线构建与实时定价。关键函数:
submitJob。// 启动行情播放任务,mockQuotes 为自定义模拟行情函数 submitJob("replayQuotesJob", "replayQuotesJob", mockQuotes, nTicks, intervalMs)
各场景脚本均按此顺序组织代码,掌握这一代码结构后,即可快理解并对照阅读三份实例实例代码。
2.2 四种市场数据类型
市场数据配置定义了需要构建的市场数据对象,是整个定价系统的基础。本教程三个业务场景实例代码中市场数据引擎使用了四种市场数据类型:
表 2-1 本教程使用的市场数据类型
| 类型 | 名称 | 输入行情 | 构建依赖 | 典型用途 |
|---|---|---|---|---|
| BondYieldCurve | 债券收益率曲线 | 基准债券行情(收益率/价格) | 基准券 INSTRUMENT 列表(bonds) | 债券估值、国债期货定价 |
| IrSingleCurrencyCurve | 单货币利率互换收益率曲线 | 存款利率(Deposit)+ 利率互换报价(IrVanillaSwap) | 贴现曲线不需要,指标曲线可以依赖,如构建 CNY_SHIBOR_3M 可以依赖 CNY_FR_007 | 人民币 FR007/SHIBOR 3M 利率互换定价 |
| IrCrossCurrencyCurve | 跨币种利率互换收益率曲线 | 外汇掉期点(FxSwap) | DiscountCurve(货币对另一方的贴现曲线) | 外汇远期/掉期、外汇期权定价 |
| FxVolatilitySurface | 外汇波动率曲面 | 外汇期权波动率报价(ATM/RR/BF) | DomesticCurve(本币折现曲线)、ForeignCurve(外币折现曲线) | 外汇期权定价与 Greeks |
注意:四种市场数据之间存在依赖关系。跨币种曲线依赖同币种对的单货币曲线作为贴现曲线;外汇波动率曲面依赖本币、外币两条折现曲线。因此在外汇场景中,市场数据需按“单货币曲线 → 跨币种曲线 → 波动率曲面”的顺序分层配置(详见第 5 章)。
各类型曲线/曲面支持的构建模型:
-
BondYieldCurve:Bootstrap(拔靴法)、Nelson-Siegel(NS)、扩展 Nelson-Siegel(NSS)等;
-
IrSingleCurrencyCurve:Bootstrap (拔靴法);
-
IrCrossCurrencyCurve:Bootstrap (拔靴法);
-
FxVolatilitySurface:SVI、SABR、Linear、CubicSpline 等,可通过配置项 model 指定。
2.3 金融工具(INSTRUMENT)
INSTRUMENT 是 DolphinDB 表示金融工具的对象类型。定价引擎创建时即绑定一批待定价工具,其内部维护“待定价工具与依赖市场数据”的关系表,当依赖的市场数据更新时自动触发相关工具的重新定价。
金融工具构建:parseInstrument 将各种金融工具产品描述转换成 INSTRUMENT 类型对象后才能用于定价引擎绑定。DolphinDB 当前已支持的各类金融工具及其对应字段的详细说明,可以参阅:INSTRUMENT 类型。本教程三个场景涉及债券、外汇远期、利率互换、外汇欧式期权示例金融工具的 JSON 格式定义如下所示:
// 债券工具示例
bond = {
"productType": "Cash", // 债券工具,固定填 "Cash"
"assetType": "Bond", // 债券工具固定填 "Bond"
"bondType": "FixedRateBond", // "FixedRateBond"固息债
"instrumentId": "240025.IB", // 待定价工具ID,债券代码
"start": 2024.12.25, // 起息日
"maturity": 2031.12.25, // 到期日
"issuePrice": 100.0, // 发行价格
"coupon": 0.0149, // 票面利率,0.0149 表示 1.49%
"frequency": "Annual", // 付息频率
"dayCountConvention": "ActualActualISDA" // 日期计数惯例
}
// 外汇远期工具示例
forward = {
"instrumentId": "FxForward001", // 外汇远期工具ID
"productType": "Forward", // 固定填 "Forward"
"forwardType": "FxForward", // 固定值 "FxForward"
"expiry": 2025.12.16, // 到期日
"delivery": 2025.12.18, // 交割日
"currencyPair": "USDCNY", // 货币对
"direction": "Buy", // 交易方向:"Buy" / "Sell"
"notionalCurrency": "USD", // 名义本金币种
"notionalAmount": 1E6, // 名义本金
"strike": 7.1 // 执行价格(远期汇率)
}
// 利率互换工具示例
swap = {
"instrumentId": "IR_INSTRUMENT_001", // 利率互换工具ID
"productType": "Swap", // 固定填 "Swap"
"swapType": "IrSwap", // 固定填 "IrSwap"
"irSwapType": "IrFixedFloatingSwap", // 固定定息对浮息类型
"start": 2025.06.16, // 起息日
"maturity": 2028.06.16, // 到期日
"frequency": "Quarterly", // 付息频率
"fixedRate": 0.018, // 固定端利率
"calendar": "CFET", // 交易日历
"fixedDayCountConvention": "Actual365", // 固定端日期计数惯例
"floatingDayCountConvention": "Actual365",// 浮动端日期计数惯例
"payReceive": "Pay", // "Pay"付固定收浮动,"Receive"收固定付浮动
"iborIndex": "FR_007", // 浮动端参考利率,可选 FR_007/SHIBOR_3M
"spread": 0.0001, // 利差
"notionalCurrency": "CNY", // 名义本金币种
"notionalAmount": 1E8 // 名义本金
}
// 外汇欧式期权工具示例
option = {
"instrumentId": "FxEuropeanOption_USDCNY_0001", // 工具ID
"productType": "Option", // 固定填 "Option"
"optionType": "EuropeanOption", // 期权类型:欧式
"assetType": "FxEuropeanOption", // 资产类型:外汇欧式期权
"notionalCurrency": "USD", // 名义本金币种
"notionalAmount": 1000000, // 名义本金金额
"strike": 7.2, // 执行价格
"maturity": 2025.10.28, // 到期日
"payoffType": "Call", // "Call" / "Put"
"dayCountConvention": "Actual365", // 日期计数惯例
"underlying": "USDCNY" // 标的货币对
}
// 通过 parseInstrument 将不同的金融工具产品描述转换成 INSTRUMENT 类型对象向量
instruments = parseInstrument([bond,forward,swap,option])
金融工具查看:通过内置函数 extractInstrument 可将 INSTRUMENT 对象解析回可读的结构化内容。
// 外汇欧式期权工具示例
option = {
"instrumentId": "FxEuropeanOption_USDCNY_0001", // 工具ID
"productType": "Option", // 固定填 "Option"
"optionType": "EuropeanOption", // 期权类型:欧式
"assetType": "FxEuropeanOption", // 资产类型:外汇欧式期权
"notionalCurrency": "USD", // 名义本金币种
"notionalAmount": 1000000, // 名义本金金额
"strike": 7.2, // 执行价格
"maturity": 2025.10.28, // 到期日
"payoffType": "Call", // "Call" / "Put"
"dayCountConvention": "Actual365", // 日期计数惯例
"underlying": "USDCNY" // 标的货币对
}
inst = parseInstrument(option)
// 解析 INSTRUMENT 类型对象内部的数据
result = extractInstrument(inst)
print(result)
/* output
productType->Option
optionType->EuropeanOption
assetType->FxEuropeanOption
version->3
instrumentId->FxEuropeanOption_USDCNY_0001
direction->Buy
notionalCurrency->USD
notionalAmount->1000000
strike->7.200000000000001
maturity->2025.10.28
delivery->2025.10.30
dayCountConvention->Actual365
payoffType->Call
underlying->USDCNY
domesticCurve->
foreignCurve->
*/
2.4 市场数据定义
MKTDATA 是 DolphinDB 表示市场数据(曲线/曲面)的对象类型,由市场数据引擎根据不同的构建配置,基于市场行情构建得到。当前支持的 MKTDATA 数据类型详细介绍可参阅:parseMktData。
市场数据构建:
-
通过市场数据引擎实时构建曲线和曲面等市场数据。当前支持的市场数据类型和配置说明可参阅createMktDataEngine 的 mktDataConfig 参数示例。
-
内置函数 parseMktData 可以将输入的字典或 JSON 字符串解析为 MKTDATA 类型。如下示例代码通过
parseMktData将 FR_007 的历史定盘数据字典对象解析为资产价格曲线(AssetPriceCurve):
// FR_007 的历史定盘数据
fr007HistCurve = {
"mktDataType": "Curve",
"curveType": "AssetPriceCurve",
"curveName": "PRICE_FR_007",
"referenceDate": 2025.08.18,
"currency": "CNY",
"dates":[2025.05.09, 2025.05.12, 2025.05.13, 2025.05.14, 2025.05.15, 2025.05.16, 2025.05.19, 2025.05.20, 2025.05.21, 2025.05.22,
2025.05.23, 2025.05.26, 2025.05.27, 2025.05.28, 2025.05.29, 2025.05.30, 2025.06.03, 2025.06.04, 2025.06.05, 2025.06.06,
2025.06.09, 2025.06.10, 2025.06.11, 2025.06.12, 2025.06.13, 2025.06.16, 2025.06.17, 2025.06.18, 2025.06.19, 2025.06.20,
2025.06.23, 2025.06.24, 2025.06.25, 2025.06.26, 2025.06.27, 2025.06.30, 2025.07.01, 2025.07.02, 2025.07.03, 2025.07.04,
2025.07.07, 2025.07.08, 2025.07.09, 2025.07.10, 2025.07.11, 2025.07.14, 2025.07.15, 2025.07.16, 2025.07.17, 2025.07.18,
2025.07.21, 2025.07.22, 2025.07.23, 2025.07.24, 2025.07.25, 2025.07.28, 2025.07.29, 2025.07.30, 2025.07.31, 2025.08.01,
2025.08.04, 2025.08.05, 2025.08.06, 2025.08.07, 2025.08.08, 2025.08.11, 2025.08.12, 2025.08.13, 2025.08.14, 2025.08.15
],
"values":[1.6000, 1.5600, 1.5300, 1.5500, 1.5500, 1.6300, 1.6500, 1.6000, 1.5900, 1.5800,
1.6300, 1.7000, 1.7000, 1.7000, 1.7500, 1.7500, 1.5900, 1.5800, 1.5700, 1.5600,
1.5500, 1.5500, 1.5600, 1.5900, 1.5900, 1.5700, 1.5500, 1.5600, 1.5679, 1.6000,
1.5700, 1.8500, 1.8300, 1.8400, 1.8500, 1.9500, 1.6036, 1.5800, 1.5200, 1.5000,
1.5000, 1.5100, 1.5100, 1.5300, 1.5200, 1.5500, 1.6000, 1.5400, 1.5400, 1.5000,
1.5000, 1.4800, 1.5000, 1.6000, 1.7500, 1.6400, 1.6200, 1.6300, 1.6000, 1.5000,
1.4800, 1.4700, 1.4800, 1.4900, 1.4600, 1.4600, 1.4600, 1.4800, 1.4800, 1.4900
] \ 100
}
// 通过 parseMktData 将 fr007HistCurve 字典定义解析为资产价格曲线 MKTDATA 对象
mktData = parseMktData(fr007HistCurve)
print(typestr(mktData)) // 通过 typestr 输出类型为:MKTDATA
市场数据提取:通过内置函数 extractMktData 可将 MKTDATA 对象解析为可读内容,用于查看引擎发布的曲线/曲面结果。市场数据引擎输出的曲线结果流表 curveResult 中的 data 列即为 MKTDATA 类型对象。
通过内置函数 extractMktData 对其中 MKTDATA 数据解析提取处理:
mktData = exec data from curveResult where eventTime=2026.09.17 17:13:48.373915601
print(extractMktData(mktData))
输出结果如图 2-2 所示:
extractMktData 打印结果2.5 双引擎协同模式
市场数据引擎构建完成曲线/曲面后,支持通过配置 handler 参数采用级联模式或非级联模式(回调模式)将 MKTDATA 数据流转至定价引擎:
级联模式:直接把定价引擎对象作为 handler 传入。市场数据引擎构建完成后自动将市场数据追加到定价引擎,此模式不回调用户自定义函数,所以不会有 curveResult 流表的写入操作。
非级联模式:传入自定义回调函数 mktDataHandler,在回调中手动调用
appendMktData 将市场数据追加到定价引擎,同时可将曲线结果写入 curveResult 流表。
表 2-2 两种模式对比
| 对比项 | 级联模式 | 非级联模式 |
|---|---|---|
| handler 参数 | 直接传定价引擎对象 | 传自定义回调(引擎以部分应用方式传入) |
| 链路 | 引擎内部自动追加,最简洁 | 回调中手动 appendMktData |
| 曲线结果记录与监控 | 不写入 curveResult 流表 | 可同时写入 curveResult 流表 |
| 适用场景 | 追求最简链路 | 需要监控/留存每次曲线构建结果 |
本教程债券、外汇场景默认采用非级联模式(便于观察曲线构建结果),IRS 场景默认采用级联模式,三种场景脚本中均保留了另一种模式的注释代码,仅需调整一行代码即可灵活切换。
2.6 数据准备
三个场景脚本均使用模拟行情(脚本内 mockQuotes 函数生成)运行;若需贴近生产回放真实历史数据,可使用教程附件 data.zip 中的模拟行情数据和金融工具基础数据,并在脚本执行前通过 loadData.dos 脚本完成数据加载。
表 2-3 附件 data.zip 行情数据说明
| 行情库表 | 说明 |
|---|---|
| loadTable("dfs://ficc_quotes","Bond") | 债券行情数据。行情要素字段:timestamp(行情时间戳)、name(债券代码)、ytm(债券收益率) |
| loadTable("dfs://ficc_quotes","Inst") | 存款利率、利率互换报价、外汇行情数据。字段:timestamp、type(枚举:IrVanillaSwap、Deposit、FxSwap、FxSpot)、name(枚举:CNY_FR_007、FR_007、USDCNY)、term(枚举:7d、1M …1y、3y)、quote(市场价格行情) |
| loadTable("dfs://ficc_quotes","FxOption") | 外汇期权波动率报价数据。字段:timestamp、type(FxOption)、subType(波动率类型,枚举:D25_RR、D10_RR、D10_BF、D25_BF、ATM)、name(枚举:USDCNY)、term(枚举:1d、1w、1M … 1y、3y)、quote(波动率行情) |
此外,债券场景的基准券信息存储于分布式库 dfs://ficc_basic 中,包含 BenchmarkBonds 与 BondInfo 两张表。
3. 债券场景实践
本章以国债基准券为样本,演示债券场景下“行情回放 → 市场数据构建 → 实时定价”的最小闭环。我们将模拟回放 14 支国债基准券的逐笔行情数据,驱动市场数据引擎实时构建名为 CNY_TREASURY_BOND 的国债收益率曲线。同时,系统将联动定价引擎,基于最新构建的收益率曲线对固定利率债券完成实时的 NPV 估值计算。
配套代码:Bond_PricingDemo.dos
3.1 场景概述
债券是 FICC 业务中体量最大的品种。本场景演示最小闭环:模拟 14 支国债基准券的逐笔行情,驱动市场数据引擎实时构建国债收益率曲线 CNY_TREASURY_BOND,并联动定价引擎对固定利率债券实时估值。
核心链路:
场景特点:
-
基准券需从分布式库 dfs://ficc_basic 中按条件筛选加载(dfs://ficc_basic 的数据在附件 data.zip 中,可先通过附件 loadData.dos 脚本进行加载),并通过
parseInstrument转为 INSTRUMENT 对象——基准券是国债收益率曲线构建的样本券,用于定义收益率曲线的期限结构(每只基准券按其剩余期限对应曲线上的一个期限锚点)。 -
曲线构建的触发条件:配置中所有基准券的行情全部到齐后,引擎才会构建一次曲线(示例中为 14 支),保证期限结构完整,避免样本缺失导致拟合出的曲线失真、估值出错。
-
曲线实时构建与下游实时定价联动:曲线随行情到达实时重建,每次重建出的最新市场数据自动推送定价引擎,立即触发依赖该曲线的债券重新估值,实现"行情 → 曲线 → 估值"全链路自动联动。
-
债券场景行情流表接入门槛最低,只需提供基准券收益率报价即可驱动整条"曲线构建 + 定价"链路。
本场景共涉及三张流表:
表 3-1 债券场景流表说明
| 流表 | 字段 | 说明 |
|---|---|---|
|
marketQuotes (债券行情流表) |
timestamp (NANOTIMESTAMP) type (STRING) name (STRING) price (DOUBLE) |
行情时间戳 行情类型 "Bond" 债券代码、收益率 |
|
pricingResult (定价结果) |
name (STRING) date (DATE) npv (DOUBLE) eventTime (NANOTIMESTAMP) sysTime (NANOTIMESTAMP) delayStart (NANOTIMESTAMP) |
定价工具名称 估值日期 NPV 事件时间 系统时间 延迟起始时间 |
|
curveResult (曲线结果) |
eventTime (NANOTIMESTAMP) kind (STRING) date (DATE) name (STRING) data (MKTDATA) sysTime (NANOTIMESTAMP) |
事件时间 市场数据类型 参考日期 市场数据名称 市场数据 MKTDATA 对象 系统时间 |
3.2 实现步骤详解
步骤①:环境清理
所有场景脚本的第一步都是清理旧对象,保证脚本可反复运行:
try { cancelJob("replayQuotesJob") } catch(ex) {}
try { dropStreamEngine("FICC_MKT_ENGINE") } catch(ex) {}
try { dropStreamEngine("FICC_PRICING_ENGINE") } catch(ex) {}
try { dropStreamTable("pricingResult") } catch(ex) {}
try { dropStreamTable("curveResult") } catch(ex) {}
try { unsubscribeTable(tableName="marketQuotes", actionName="AppendQuotesToMktDataEngine") } catch(ex) {}
try { dropStreamTable("marketQuotes") } catch(ex) {}
go;
清理流表前必须先取消相关订阅,否则订阅句柄残留会导致后续同名流表订阅异常。
步骤②:加载基准券并构建 INSTRUMENT
从分布式库筛选当前周期的国债基准券,并加载其基础信息:
// 获取基准券列表:国债 + 基准债券 + 当月适用
baseBondList = exec bondCode from loadTable("dfs://ficc_basic", "BenchmarkBonds")
where curveType = "国债" and bondCategory = '基准债券' and applyPeriod = month(referenceDate)
// 加载基准券基础信息,按到期日排序
baseBondInfo = select "Cash" as productType, "Bond" as assetType, bondType, 0 as version,
bondCode as instrumentId, startDate as start, maturityDate as maturity, issuePrice,
coupon, frequency, "ActualActualISDA" as dayCountConvention
from loadTable("dfs://ficc_basic", "BondInfo")
where bondCode in baseBondList order by maturityDate
// 解析为 INSTRUMENT 对象
bonds = parseInstrument(baseBondInfo)
基准券 INSTRUMENT 将作为曲线配置中的 bonds 字段传入市场数据引擎。
步骤③:创建定价引擎
在级联模式时,createMktDataEngine 创建市场数据引擎时,入参 handler 需要传入定价引擎对象,所以需要先完成创建定价引擎,在创建定价引擎时需要通过入参 instrument 绑定待定价金融工具,本场景中通过 buildBondInstruments 函数来模拟构造待定价债券工具。
// 函数:模拟方式构建债券工具
// nSize: 工具数量
// refDate: 模拟数据的参考日期
def buildBondInstruments(nSize, refDate) {
idx = 0..(nSize - 1)
instTb = table(
take("Cash", nSize) as productType,
take("Bond", nSize) as assetType,
take("FixedRateBond", nSize) as bondType,
"24" + lpad(string(idx), 8, "0") + ".IB" as instrumentId,
take(refDate - 365, nSize) as start,
refDate + (idx % 30 + 1) * 365 as maturity,
take(100.0, nSize) as issuePrice,
round(0.015 + (idx % 31) * 0.0005, 4) as coupon,
take("Annual", nSize) as frequency,
take("ActualActualISDA", nSize) as dayCountConvention
)
return parseInstrument(instTb)
}
// 创建待定价债券
pricingBonds = buildBondInstruments(nPricing, referenceDate)
// 定价引擎运行配置参数
priceEngCfg = {
"numThreads": 8, // 定价引擎线程数
"useSystemTime": true, // 是否使用系统时间作为事件时间
"outputTime": true, // 是否输出 eventTime 列表示触发的事件时间
"outputDelayStart": true // 是否输出延迟开始时间戳(性能观测用)
}
// 创建定价引擎
pricingEngine = createPricingEngine(name="FICC_PRICING_ENGINE", instrument=pricingBonds, handler=pricingHandler, engineConfig=priceEngCfg)
定价结果回调函数 pricingHandler 将每次定价输出写入 pricingResult 结果流表。
def pricingHandler(eventTime, name, date, npv, delayStart) {
tableInsert(pricingResult, name, date, npv, eventTime, now(true), delayStart)
}
步骤④:配置债券收益率曲线并创建市场数据引擎
// 国债收益率曲线构建配置
curveCfg = {
"name": "CNY_TREASURY_BOND", // 国债收益率曲线名称
"type": "BondYieldCurve", // 曲线类型:国债收益率曲线
"bonds": bonds, // 基准券列表(INSTRUMENT 对象)
"currency": "CNY",
"dayCountConvention": "Actual365"
}
// 市场数据引擎运行配置参数
mktEngCfg = {
"numThreads": 8, // 市场数据引擎线程数
"useSystemTime": false, // 不使用系统时间,改用行情时间戳作为事件时间
"timeColumn": "timestamp", // useSystemTime=false 时必须指定时间列
"outputTime": true // 输出事件时间列
}
// 非级联模式:通过 mktDataHandler 推送市场数据并记录曲线结果
mktEngine = createMktDataEngine(name="FICC_MKT_ENGINE", referenceDate=referenceDate,
mktDataConfig=[curveCfg], handler=mktDataHandler{ pricingEngine }, engineConfig=mktEngCfg)
// 级联模式:handler 直接传 pricingEngine
// mktEngine = createMktDataEngine(name="FICC_MKT_ENGINE", referenceDate=referenceDate, mktDataConfig=[curveCfg], handler=pricingEngine, engineConfig=mktEngCfg)
其中国债收益率曲线构建配置 curveCfg 说明如下表 3-2 所示:
表 3-2 国债收益率曲线构建配置说明
| 字段 | 说明 |
|---|---|
| name | 曲线名称,定价工具通过该名称引用曲线 |
| type | 市场数据类型,债券场景固定为 "BondYieldCurve" |
| bonds | 基准券 INSTRUMENT 列表,曲线构建的样本券 |
| currency | 币种 |
| dayCountConvention | 计息日数规则 |
步骤⑤:订阅行情流表
subscribeTable(tableName="marketQuotes", actionName="AppendQuotesToMktDataEngine",
offset=-1, handler=append!{mktEngine}, msgAsTable=true)
行情到达后以 append! 方式直接推送进市场数据引擎。
步骤⑥:模拟行情回放
本场景中通过指定 nTicks(轮次)、intervalMs(行情间隔)参数来模拟不同行情量、频率下 14 支基准债券的实时行情,每轮次将 14 支基准券逐笔注入(券间间隔 intervalMs 毫秒)。
def mockQuotes(nTicks, intervalMs) {
streamQuoteTb = objByName("marketQuotes")
bondNames = ["259916.IB","240021.IB","250001.IB","250013.IB","250012.IB","250010.IB",
"250014.IB","2500802.IB","250011.IB","2400102.IB","2500004.IB","2500005.IB",
"200007.IB","2400003.IB"]
baseQuotes = [0.012958,0.013761,0.013798,0.013913,0.014312,0.014429,0.016269,
0.017587,0.017829,0.019693,0.021340,0.021007,0.021634,0.021793]
for (i in 0..(nTicks - 1)) {
quotes = baseQuotes + (i % 10) * 0.00001
for (j in 0..13) {
tableInsert(streamQuoteTb, now(true), "Bond", bondNames[j], quotes[j])
sleep(intervalMs)
}
}
}
submitJob("replayQuotesJob", "replayQuotesJob", mockQuotes, 1, 10)
在市场数据引擎具备14支样本券的完整行情后,随即触发 CNY_TREASURY_BOND 曲线构建,并级联触发待定价债券的估值计算。
3.3 运行结果
在上述 3.2 章节代码执行完成后,系统会实时将曲线构建结果和债券重定价结果输出至 curveResult 和 pricingResult 流表中(注:非级联模式下才会有 curveResult 数据)。
债券定价结果(pricingResult)
通过查询定价结果流表,可以获取引擎随行情波动实时计算的最新债券价值:
select * from pricingResult order by sysTime
查询结果数据示例如图 3-2 所示:
定价结果数据字段说明:
-
name:待定价债券的代码(本场景中为模拟债券代码)。
-
date:估值日期,等于 referenceDate。
-
npv:债券的净现值(NPV)。
-
eventTime:触发本次定价的事件时间戳。
-
sysTime:定价结果记录写入的系统时间戳。
-
delayStart:每笔定价对应触发的原始行情时间戳(性能观测用)。
收益率曲线结果(curveResult)
非级联模式下,市场数据引擎每次构建 CNY_TREASURY_BOND 曲线后,回调函数 mktDataHandler 会将构建结果写入 curveResult 流表。
select eventTime, kind, date, name, data, sysTime from curveResult
查询结果数据示例如图 3-3 所示:
曲线结果字段说明:
-
eventTime:触发构建该市场数据的行情时间戳,由市场数据引擎运行配置的 useSystemTime、 timeColumn 参数配置确定是否使用系统时间还是行情时间。
-
kind:标识市场数据类型,当前场景为 Curve(收益率曲线数据)
-
date:参考日期,等于 referenceDate。
-
name:曲线名称,由曲线构建配置 curveCfg 中的 name 参数决定。
-
data:曲线数据,为 MKTDATA 类型,可以通过
extractMktData解析查看,可参考 2.4 市场数据定义 章节中市场数据提取的内容。 -
sysTime:市场数据记录写入时的系统时间戳。
3.4 场景要点小结
本场景中构建名称为 "CNY_TREASURY_BOND" 的债券收益率曲线,依赖 14 支国债基准券基础信息,本场景中的定价日期
referenceDate 为 2025.08.18 ,在
loadTable("dfs://ficc_basic","BenchmarkBonds") 基准债券参数表(图
3-4)中维护了2025年8月份有效的14支国债基准券,在
loadTable("dfs://ficc_basic","BondInfo") 债券基础信息表(图
3-5)中维护了具体 14 支基准债券基础信息。若用户需要试用不同的定价日期进行测试时,只需在这两张表中维护对应基准债券数据即可正常使用。
曲线构建以“全部基准券行情到齐”为触发条件,若长时间未见曲线产出,应优先检查是否有基准券行情缺失。待定价债券到期日、票面利率等静态要素在创建定价引擎时一次性绑定,行情更新只触发重定价,不改变工具定义。
4. IRS (利率互换)场景实践
利率互换(IRS)是银行间市场最主要的利率衍生品,本章以人民币 FR_007 利率互换为对象,演示 IRS 场景下“存款利率 + 利率互换报价 → 单货币曲线 → 实时估值”的流式定价流程。实践中将模拟回放包含 12 个期限点的 FR_007 存款利率与利率互换报价数据,驱动市场数据引擎实时构建人民币 FR_007 利率互换曲线(CNY_FR_007)。在此基础上,联动定价引擎对固定对浮动利率互换合约(IrFixedFloatingSwap)进行实时估值。
配套代码:IRS_PricingDemo.dos
4.1 场景概述
本场景模拟回放 FR_007 存款利率与利率互换报价(12 个期限点),驱动市场数据引擎实时构建人民币 FR_007 利率互换曲线 CNY_FR_007,并联动定价引擎对固定对浮动利率互换(IrFixedFloatingSwap)估值。
核心链路:
与债券场景相比,IRS 场景有两个新特点:
-
行情流表扩展为 5 列(增加 term),以承载 Deposit 与 IrVanillaSwap 两类报价的期限属性;
-
定价日前首笔浮动现金流需要历史定盘利率,须在回放前通过
appendMktData注入 FR_007 历史定盘曲线(本场景最重要的步骤,见 4.2 步骤④)
与债券场景相比,本场景行情流表扩展为 5 列,新增 term 期限列,另外定价结果流表、曲线结果流表保持不变。
表 4-1 利率互换场景行情流表结构说明
| 字段 | 示例值 | 说明 |
|---|---|---|
| timestamp | 2025.08.18T09:30:00.000 | 行情时间,类型:NANOTIMESTAMP |
| type | "Deposit" / "IrVanillaSwap" | 工具类型,类型:STRING |
| name | "FR_007" / "CNY_FR_007" | 报价所属曲线名,类型:STRING |
| term | "7d" / "1M" ... "10y" | 期限点,类型:DURATION |
| price | 0.0235 | 报价(小数形式),类型:DOUBLE |
4.2 实现步骤详解
步骤①:环境清理,所有场景脚本的第一步都是清理旧对象,此处不再赘述。
步骤②:构建待定价 IRS 工具+创建定价引擎
本场景中通过模拟方式构建待定价 IRS 工具,并将其作为 createPricingEngine 的 instrument 入参创建定价引擎。
// 函数:模拟方式构建固定利率对浮动利率 IRS 合约,用于定价引擎计算测试
// nSize: 工具数量
def buildIrSwapInstruments(nSize) {
idx = 0..(nSize - 1)
instTb = table(
take("Swap", nSize) as productType,
take("IrSwap", nSize) as swapType,
take("IrFixedFloatingSwap", nSize) as irSwapType,
"FR_007_" + format(2028.06.16, "yyyyMM") + "_" + lpad(string(idx), 8, "0") as instrumentId,
take(2025.06.16, nSize) as start,
take(2028.06.16, nSize) as maturity,
take("Quarterly", nSize) as frequency,
take(0.018, nSize) as fixedRate,
take("CFET", nSize) as calendar,
take("Actual365", nSize) as fixedDayCountConvention,
take("Actual365", nSize) as floatingDayCountConvention,
take("Pay", nSize) as payReceive,
take("FR_007", nSize) as iborIndex,
take(0.0001, nSize) as spread,
take("CNY", nSize) as notionalCurrency,
take(1E8, nSize) as notionalAmount
)
return parseInstrument(instTb)
}
// 创建待定价利率互换金融工具(固定利率互换)
nPricing = 2 // 模拟目标金融工具数量
irSwapInst = buildIrSwapInstruments(nPricing)
// 创建定价引擎
pricingEngine = createPricingEngine(name="FICC_PRICING_ENGINE", instrument=irSwapInst, handler=pricingHandler, engineConfig=priceEngCfg)
步骤③:配置 CNY_FR_007 曲线并创建市场数据引擎
CNY_FR_007 曲线的样本点由 1 个存款利率 + 11 个利率互换期限点构成:
// 单货币利率互换曲线构建配置(当前Demo中,构建一条以人民币计价、参考 FR007 浮动利率的利率互换曲线)
curveCfg = {
'name': "CNY_FR_007",
'type': "IrSingleCurrencyCurve",
'currency': "CNY",
'insts': [
("FR_007", "Deposit", "7d"),
("CNY_FR_007", "IrVanillaSwap", "1M"), ("CNY_FR_007", "IrVanillaSwap", "3M"),
("CNY_FR_007", "IrVanillaSwap", "6M"), ("CNY_FR_007", "IrVanillaSwap", "9M"),
("CNY_FR_007", "IrVanillaSwap", "1y"), ("CNY_FR_007", "IrVanillaSwap", "2y"),
("CNY_FR_007", "IrVanillaSwap", "3y"), ("CNY_FR_007", "IrVanillaSwap", "4y"),
("CNY_FR_007", "IrVanillaSwap", "5y"), ("CNY_FR_007", "IrVanillaSwap", "7y"),
("CNY_FR_007", "IrVanillaSwap", "10y")
],
'dayCountConvention': "Actual365"
}
// 创建市场数据引擎(模式一、引擎级联模式,级联定价引擎pricingEngine)
mktEngine = createMktDataEngine("FICC_MKT_ENGINE", referenceDate, [curveCfg], pricingEngine, NULL, mktEngCfg)
// 创建市场数据引擎(模式二、非级联模式,通过 mktDataHandler appendMktData 市场数据到定价引擎pricingEngine)
// mktEngine = createMktDataEngine("FICC_MKT_ENGINE", referenceDate, [curveCfg], mktDataHandler{ pricingEngine }, NULL, mktEngCfg)
insts 中每个三元组为 (报价名称, 工具类型, 期限),与行情流表中的 type、name、term 列一一对应。短端用 FR_007 存款利率(7d)锚定,1M 至 10y 用利率互换报价构建。
步骤④:预注入 FR_007 历史定盘利率(关键步骤)
浮动端已起息、尚未到下一个重置日的现金流,其浮动利率取自定价日之前最近一次定盘。因此必须在回放前把 FR_007 的历史定盘数据构造为
AssetPriceCurve 曲线(曲线名 PRICE_FR_007),通过 parseMktData
和 appendMktData 预先注入定价引擎:
fr007HistCurve = {
"mktDataType": "Curve",
"curveType": "AssetPriceCurve",
"curveName": "PRICE_FR_007",
"referenceDate": referenceDate,
"currency": "CNY",
"dates": [2025.05.09, 2025.05.12, ... , 2025.08.15],
"values": [1.6000, 1.5600, ... , 1.4900] \ 100
}
appendMktData(pricingEngine, parseMktData(fr007HistCurve))
注意:FR_007 利率互换付息频率为季度(约 63 个自然日、约 43 个交易日一笔现金流区间),为保证定价引擎在任意定价日都能取到“定价日前最近定盘”,建议传入的历史定盘数据不少于 70 个交易日。
步骤⑤:订阅行情并模拟回放
mockQuotes 每轮回放 12
个期限点报价(7d、1M、3M、6M、9M、1y、2y、3y、4y、5y、7y、10y),逐笔注入并间隔 intervalMs 毫秒。12
个期限点报价到齐后触发 CNY_FR_007 曲线重建,定价引擎自动对 IRS 重新估值:
// 订阅行情流表
subscribeTable(tableName="marketQuotes", actionName="AppendQuotesToMktDataEngine", offset=-1, handler=append!{mktEngine}, msgAsTable=true, batchSize=1)
// 自定义模拟行情函数, 参数 nTicks 模拟行情数量, 参数 intervalMs 模拟逐笔行情流间隔(毫秒)
def mockQuotes(nTicks, intervalMs) {
streamQuoteTb = objByName("marketQuotes")
terms = [ 7d, 1M, 3M, 6M, 9M, 1y, 2y, 3y, 4y, 5y, 7y, 10y ]
// 12 个期限点报价(7d、1M、3M、6M、9M、1y、2y、3y、4y、5y、7y、10y)
instTypes = ["Deposit", "IrVanillaSwap", "IrVanillaSwap", "IrVanillaSwap", "IrVanillaSwap", "IrVanillaSwap", "IrVanillaSwap", "IrVanillaSwap", "IrVanillaSwap", "IrVanillaSwap", "IrVanillaSwap", "IrVanillaSwap"]
instNames = ["FR_007", "CNY_FR_007", "CNY_FR_007", "CNY_FR_007", "CNY_FR_007", "CNY_FR_007", "CNY_FR_007", "CNY_FR_007", "CNY_FR_007", "CNY_FR_007", "CNY_FR_007", "CNY_FR_007"]
quotes007 = [2.3500, 2.3396, 2.3125, 2.3613, 2.4075, 2.4513, 2.5750, 2.6763, 2.7650, 2.8463, 2.9841, 3.1350] \ 100
for (i in 0..(nTicks - 1)) {
quotes = quotes007 + (i % 10) * 0.00001 // 每个价格微调,模拟行情变化
for (j in 0..(instTypes.size() - 1)) {
tableInsert(streamQuoteTb, now(true), instTypes[j], instNames[j], terms[j], quotes[j])
sleep(intervalMs)
}
}
}
// 启动行情播放任务,驱动市场数据构建和IRS定价。
nTicks = 100 // 模拟行情数量
intervalMs = 10 // 模拟逐笔行情流间隔(毫秒)
submitJob("replayQuotesJob", "replayQuotesJob", mockQuotes, nTicks, intervalMs)
4.3 运行结果
在上述 4.2 章节代码执行完成后,系统会实时将曲线构建结果和利率互换工具定价结果输出至 curveResult 和 pricingResult 流表中(注:非级联模式下才会有 curveResult 数据)。
IRS 定价结果 (pricingResult)
通过查询定价结果流表,可以获取引擎随行情波动实时计算的最新债券价值:
select * from pricingResult order by sysTime
查询结果数据示例如图 4-2 所示:
定价结果数据字段说明:
-
name:待定价利率互换(IRS)合约(本场景中为模拟 IRS 代码)。
-
date:估值日期,等于 referenceDate。
-
npv:利率互换合约的净现值(NPV)。
-
eventTime:触发本次定价的事件时间戳。
-
sysTime:定价结果记录写入的系统时间戳。
-
delayStart:每笔定价对应触发的原始行情时间戳(性能观测用)。
CNY_FR_007 曲线结果 (curveResult)
非级联模式下,市场数据引擎每次构建 CNY_FR_007 曲线后,回调函数 mktDataHandler 会将构建结果写入 curveResult 流表。
select eventTime, kind, date, name, data, sysTime from curveResult
查询结果数据示例如图 4-3 所示:
曲线结果字段说明:
-
eventTime:触发构建该市场数据的行情时间戳,由市场数据引擎运行配置 mktEngCfg 的 useSystemTime、 timeColumn 参数配置确定是否使用系统时间还是行情时间。
-
kind:标识市场数据类型,当前场景为 Curve (收益率曲线数据)
-
date:参考日期,等于 referenceDate。
-
name:曲线名称,由曲线构建配置 curveCfg 中的 name 参数决定。
-
data:曲线数据,为 MKTDATA 类型,可以通过
extractMktData解析查看,可参考 2.4 市场数据定义 章节中市场数据提取的内容。 -
sysTime:市场数据记录写入时的系统时间戳。
4.4 场景要点小结
-
CNY_FR_007 单货币利率互换曲线构建配置的 insts 三元组与行情 type / name / term 严格对应,行情流表 schema 必须包含这些列;
-
历史定盘(PRICE_FR_007,AssetPriceCurve)必须在行情回放之前注入定价引擎;
-
本场景采用级联模式,曲线结果不写入 curveResult;如需存档与监控,切换为非级联模式即可。
5. 外汇(FX)场景实践
上面我们介绍了单个交易类型的实时曲线构建和实时定价。也支持通过这两个流计算引擎对多资产进行实时估值。本章以 USDCNY 外汇远期和欧式期权为定价对象,演示外汇场景下市场数据的协同构建与实时定价。Fx_PricingDemo.dos 代码中,先构建 CNY_FR_007 单货币曲线,再以其为贴现基础构建 USD_USDCNY_FX 跨币种曲线,最后结合本币、外币折现曲线构建 USDCNY 外汇波动率曲面,FxSpot 即期行情则直接进入定价引擎,作为远期与期权的估值基准。
配套代码:Fx_PricingDemo.dos
5.1 场景概述与市场数据依赖
外汇场景是三个场景中市场数据结构最复杂的:一个货币对(USDCNY)的远期与期权定价,需要三层市场数据协同支撑:
-
CNY_FR_007 单货币利率曲线——人民币折现曲线;
-
USD_USDCNY_FX 跨币种利率曲线——由外汇掉期点(FxSwap)构建,且依赖 CNY_FR_007 作为贴现曲线;
-
USDCNY 外汇波动率曲面——由 ATM/RR/BF 波动率报价构建,依赖上述两条曲线作为本币/外币折现曲线。
行情流表结构:由于本场景需同时承载利率、掉期点、波动率与即期四类行情,行情流表 marketQuotes 相比 IRS 场景的 5 列,增加了 subType 列 (timestamp、type、subType、name、term、price)。
其中 subType、term 两列的填充约定:
表 5-1 外汇场景行情流表 subType、term 列填充约定
| 新增列 | 填充约定 | 说明 |
|---|---|---|
| subType | 波动率报价填报价名称(ATM、D25_RR、D25_BF、D10_RR、D10_BF),其余行情留空,数据类型为:STRING | 与波动率曲面配置中的 names 对应,用于区分同一期限下的 5 类报价 |
| term | 期限点:利率行情 7d~10y、掉期点与波动率 1d~3y、即期填 0d,数据类型为:DURATION | 与曲线配置 insts 及曲面配置 terms 对应 |
5.2 实现步骤详解
所有场景脚本的第一步都是环境清理,此处不再赘述。
步骤①:构建待定价工具(外汇远期与欧式期权)
通过两个自定义函数模拟生成待定价工具(buildForwardInstruments、buildOptionInstruments),核心字段即 2.3 节的外汇远期与外汇欧式期权 JSON 示例。这两类待定价工具将作为 createPricingEngine 时的 instrument 的入参参数。
forwardInst = buildForwardInstruments(nPricing) // 外汇远期
optionInst = buildOptionInstruments(nPricing) // 欧式外汇期权
buildForwardInstruments 模拟生成外汇远期合约工具函数:
// 模拟生成外汇远期合约工具
// nPricing: 工具数量
// baseStrike: 基准汇率(默认 7.1)
// baseExpiry: 基准到期日(默认 2025.12.16)
def buildForwardInstruments(nPricing, baseStrike=7.1, baseExpiry=2025.12.16) {
idx = 0..(nPricing - 1)
expiryVec = baseExpiry + rand(0..30, nPricing)
dirVec = take(["Buy","Sell"], nPricing)[rand(0..1, nPricing)]
buyOffset = rand(0..100, nPricing).double()
sellOffset = rand(-100..0, nPricing).double()
offsetVec = iif(dirVec == "Sell", buyOffset, sellOffset)
strikeVec = round(baseStrike * (1 + offsetVec / 10000), 4)
instTb = table(
"FxFwd_" + dirVec + "_" + format(expiryVec, "yyyyMMdd") + "_" + lpad(string(idx), 8, "0") as instrumentId,
take("Forward", nPricing) as productType,
take("FxForward", nPricing) as forwardType,
expiryVec as expiry,
expiryVec + 2 as delivery,
take("USDCNY", nPricing) as currencyPair,
dirVec as direction,
take("USD", nPricing) as notionalCurrency,
(rand(1..50, nPricing).double()) * 1E5 as notionalAmount,
strikeVec as strike
)
return parseInstrument(instTb)
}
buildOptionInstruments 模拟生成欧式外汇期权工具函数:
// 模拟生成欧式外汇期权工具
// nPricing: 工具数量
// baseStrike: 基准汇率(默认 7.2)
// baseMaturity: 基准到期日(默认 2025.10.28)
def buildOptionInstruments(nPricing, baseStrike=7.2, baseMaturity=2025.10.28) {
idx = 0..(nPricing - 1)
maturityVec = baseMaturity + rand(0..30, nPricing)
payoffVec = take(["Call","Put"], nPricing)[rand(0..1, nPricing)]
callOffset = rand(0..500, nPricing).double() / 10000 // Call 偏移区间:0 ~ +5%
putOffset = rand(-500..0, nPricing).double() / 10000 // Put 偏移区间:-5% ~ 0
offsetVec = iif(payoffVec == "Call", callOffset, putOffset)
strikeVec = round(baseStrike * (1 + offsetVec), 4)
instTb = table(
"Option_" + payoffVec + "_" + format(maturityVec, "yyyyMMdd") + "_" + lpad(string(idx), 8, "0") as instrumentId,
take("Option", nPricing) as productType,
take("EuropeanOption", nPricing) as optionType,
take("FxEuropeanOption", nPricing) as assetType,
take("USD", nPricing) as notionalCurrency,
(rand(1..50, nPricing).double()) * 1E5 as notionalAmount,
strikeVec as strike,
maturityVec as maturity,
payoffVec as payoffType,
take("Actual365", nPricing) as dayCountConvention,
take("USDCNY", nPricing) as underlying
)
return parseInstrument(instTb)
}
步骤②:设置 Greeks 计算并创建定价引擎
通过设置 setting 参数为 calcSetting ,即可指定在期权定价时额外输出 Greeks 指标。
calcSetting = {
// 指定外汇欧式期权计算 Greeks:除 NPV 外,额外输出 Delta、Gamma、Vega、Theta、RhoDomestic(本币利率敏感度)、RhoForeign(外币利率敏感度)
"Option.FxEuropeanOption": {
"calcDelta": true,
"calcGamma": true,
"calcVega": true,
"calcTheta": true,
"calcRhoDomestic": true,
"calcRhoForeign": true
}
}
配置 Greeks 指标输出后 pricingHandler 的签名相应扩展为 11 个输出参数(增加 delta、gamma、vega、theta、rhoDomestic、rhoForeign 6 个 Greeks 输出项),定价结果流表也需同步扩展列:
// 定价结果流表也需相应扩展字段列
share streamTable(1:0,`name`date`npv`delta`gamma`vega`theta`rhoDomestic`rhoForeign`eventTime`sysTime`delayStart,
[STRING, DATE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
NANOTIMESTAMP, NANOTIMESTAMP, NANOTIMESTAMP]) as pricingResult
// 定价引擎自定义输出 handler 函数,
def pricingHandler(eventTime, name, date, npv, delta, gamma, vega, theta, rhoDomestic, rhoForeign, delayStart) {
tableInsert(pricingResult, name, date, npv, delta, gamma, vega, theta, rhoDomestic, rhoForeign, eventTime, now(true), delayStart)
}
根据设定的待定价工具、Greeks 计算参数创建定价引擎实例:
// 创建定价引擎,入参包括:待定价工具、定价设置 calcSetting
pricingEngine = createPricingEngine(name="FICC_PRICING_ENGINE",
instrument=optionInst <- forwardInst, handler=pricingHandler,
engineConfig=priceEngCfg, setting=calcSetting)
步骤③:市场数据构建配置
本场景的市场数据配置 curveCfg 是一个三元素列表,分别对应 CNY_FR_007、USD_USDCNY_FX 利率曲线,USDCNY 外汇波动率曲面的构建配置,在 createMktDataEngine 创建市场数据引擎时作为 mktDataConfig 入参参数。
// 利率互换收益率曲线、外汇波动率曲面的构建配置参数
curveCfg = [
// ① CNY_FR_007 单货币利率互换收益率曲线(与 IRS 场景相同)
{
'name': "CNY_FR_007",
'type': "IrSingleCurrencyCurve",
'currency': "CNY",
'insts': [
("FR_007", "Deposit", "7d"),
("CNY_FR_007", "IrVanillaSwap", "1M"), ("CNY_FR_007", "IrVanillaSwap", "3M"),
("CNY_FR_007", "IrVanillaSwap", "6M"), ("CNY_FR_007", "IrVanillaSwap", "9M"),
("CNY_FR_007", "IrVanillaSwap", "1y"), ("CNY_FR_007", "IrVanillaSwap", "2y"),
("CNY_FR_007", "IrVanillaSwap", "3y"), ("CNY_FR_007", "IrVanillaSwap", "4y"),
("CNY_FR_007", "IrVanillaSwap", "5y"), ("CNY_FR_007", "IrVanillaSwap", "7y"),
("CNY_FR_007", "IrVanillaSwap", "10y")
],
'dayCountConvention': "Actual365"
},
// ② USD_USDCNY_FX 跨币种利率互换收益率曲线
{
'name': "USD_USDCNY_FX",
'type': "IrCrossCurrencyCurve", // 跨币种曲线
'currency': "USD",
'currencyPair': "USDCNY", // 货币对
'insts': [ // 对应 irCrossCurrencyCurveBuilder 函数的 (instNames, instTypes, terms)
("USDCNY", "FxSwap", "1d"), ("USDCNY", "FxSwap", "1w"), ("USDCNY", "FxSwap", "2w"),
("USDCNY", "FxSwap", "3w"), ("USDCNY", "FxSwap", "1M"), ("USDCNY", "FxSwap", "2M"),
("USDCNY", "FxSwap", "3M"), ("USDCNY", "FxSwap", "6M"), ("USDCNY", "FxSwap", "9M"),
("USDCNY", "FxSwap", "1y"), ("USDCNY", "FxSwap", "18M"), ("USDCNY", "FxSwap", "2y"),
("USDCNY", "FxSwap", "3y")
],
'discountCurve': "CNY_FR_007", // 依赖: 货币对中另一方的贴现曲线
'dayCountConvention': "Actual365"
},
// ③ USDCNY 外汇波动率曲面
{
'name': "USDCNY",
'type': "FxVolatilitySurface", // 外汇波动率曲面
'currencyPair': "USDCNY",
'names': ["ATM", "D25_RR", "D25_BF", "D10_RR", "D10_BF"], // 波动率报价名称
'terms': ['1d', '1w', '2w', '3w', '1M', '2M', '3M', '6M', '9M', '1y', '18M', '2y', '3y'],
'domesticCurve': "CNY_FR_007", // 依赖:本币折现曲线
'foreignCurve': "USD_USDCNY_FX", // 依赖:外币折现曲线
'model': "SVI" // 模型:SVI / SABR / Linear / CubicSpline
}
]
// 市场数据引擎运行配置参数
mktEngCfg = {
"numThreads": 8, // 工作线程数
"useSystemTime": false, // 是否使用系统时间作为事件时间
"timeColumn": "timestamp", // 定时间列(NANOTIMESTAMP)作为事件时间,指定该列后,输入数据中需要包含该列; 在useSystemTime为false时,必须指定指timeColumn参数
"outputTime": true // 是否输出 eventTime 列表示触发的事件时间
}
// 创建市场数据引擎(模式一、引擎级联模式,级联定价引擎pricingEngine)
mktEngine = createMktDataEngine(name="FICC_MKT_ENGINE", referenceDate=referenceDate, mktDataConfig=curveCfg, handler=pricingEngine, engineConfig=mktEngCfg)
// 创建市场数据引擎(模式二、非级联模式,通过 mktDataHandler appendMktData 市场数据到定价引擎pricingEngine)
//mktEngine = createMktDataEngine(name="FICC_MKT_ENGINE", referenceDate=referenceDate, mktDataConfig=curveCfg, handler=mktDataHandler{ pricingEngine }, engineConfig=mktEngCfg)
配置要点:
表 5-2 外汇场景市场数据构建配置要点
| 配置项 | 说明 |
|---|---|
| IrCrossCurrencyCurve.discountCurve | 跨币种曲线通过掉期点隐含外币利率,需以本币贴现曲线(CNY_FR_007)为贴现基础 |
| FxVolatilitySurface.names/terms | 波动率报价矩阵的行(5 类报价)与列(13 个期限),与行情的 subType、term 对应 |
| FxVolatilitySurface.domesticCurve / foreignCurve | 期权定价所需的本币、外币折现曲线,直接引用前两条曲线名 |
| FxVolatilitySurface.model | SVI 拟合波动率微笑,也可改用 SABR、Linear、CubicSpline |
步骤④:订阅行情并模拟行情输入
市场数据引擎订阅行情流表数据,然后通过行情模拟函数 mockQuotes 模拟 FR_007 利率、外汇掉期点、外汇期权波动率报价、即期汇率四类行情依次注入行情流表:
表 5-3 四类行情 type / subType 示例
| 行情类别 | type / subType | 笔数/轮 | 示例 |
|---|---|---|---|
| FR_007 利率 |
type:Deposit / IrVanillaSwap subType:空 |
12 | 7d 存款 2.35%,互换 1M~10y |
| 外汇掉期点 |
type:FxSwap subType:空 |
13 | 1d ~ 3y,如 -5.54 ~ -3962.5 点 |
| 外汇期权波动率报价 |
type:FxOption subType:ATM/D25_RR/D25_BF/D10_RR/D10_BF |
13×5=65 | 1M ATM 2.22% 等 |
| 即期汇率 |
type:FxSpot subType:空 |
1 | 7.1627, term 填 0d |
// 订阅行情流表
subscribeTable(tableName="marketQuotes", actionName="AppendQuotesToMktDataEngine", offset=-1, handler=append!{mktEngine}, msgAsTable=true)
// 自定义模拟行情函数, 参数 nTicks 模拟行情数量, 参数 intervalMs 模拟逐笔行情流间隔(毫秒)
def mockQuotes(nTicks, intervalMs) {
streamQuoteTb = objByName("marketQuotes")
// CNY_FR_007 (FR007 浮动利率)
terms = [ 7d, 1M, 3M, 6M, 9M, 1y, 2y, 3y, 4y, 5y, 7y, 10y ]
instTypes = ["Deposit", "IrVanillaSwap", "IrVanillaSwap", "IrVanillaSwap", "IrVanillaSwap", "IrVanillaSwap", "IrVanillaSwap", "IrVanillaSwap", "IrVanillaSwap", "IrVanillaSwap", "IrVanillaSwap", "IrVanillaSwap"]
instNames = ["FR_007", "CNY_FR_007", "CNY_FR_007", "CNY_FR_007", "CNY_FR_007", "CNY_FR_007", "CNY_FR_007", "CNY_FR_007", "CNY_FR_007", "CNY_FR_007", "CNY_FR_007", "CNY_FR_007"]
quotes007 = [2.3500, 2.3396, 2.3125, 2.3613, 2.4075, 2.4513, 2.5750, 2.6763, 2.7650, 2.8463, 2.9841, 3.1350] \ 100
// FxSwap Point (USDCNY 外汇掉期点)
fxSwapTerms = [ 1d, 1w, 2w, 3w, 1M, 2M, 3M, 6M, 9M, 1y, 18M, 2y, 3y ]
fxSwapPoints = [-5.54, -39.00, -75.40, -113.20, -177.00, -317.00, -466.00, -898.50, -1284.99, -1676.00, -2320.00, -2870.00, -3962.50] \ 10000
// Volatility Surface Price (USDCNY 外汇期权的波动率报价)
fxVsTerms = [ 1d, 1w, 2w, 3w, 1M, 2M, 3M, 6M, 9M, 1y, 18M, 2y, 3y ]
fxVsNames = ["ATM", "D25_RR", "D25_BF", "D10_RR", "D10_BF"]
fxVsQuotes = [0.030000, -0.007500, 0.003500, -0.010000, 0.005500,
0.020833, -0.004500, 0.002000, -0.006000, 0.003800,
0.022000, -0.003500, 0.002000, -0.004500, 0.004100,
0.022350, -0.003500, 0.002000, -0.004500, 0.004150,
0.024178, -0.003000, 0.002200, -0.004750, 0.005500,
0.027484, -0.002650, 0.002220, -0.004000, 0.005650,
0.030479, -0.002500, 0.002400, -0.003500, 0.005750,
0.035752, -0.000500, 0.002750, 0.000000, 0.006950,
0.038108, 0.001000, 0.002800, 0.003000, 0.007550,
0.039492, 0.002250, 0.002950, 0.005000, 0.007550,
0.040500, 0.004000, 0.003100, 0.007000, 0.007850,
0.041750, 0.005250, 0.003350, 0.008000, 0.008400,
0.044750, 0.006250, 0.003400, 0.009000, 0.008550]
fxVsQuotes = reshape(fxVsQuotes, size(fxVsNames):size(fxVsTerms)).transpose()
// 按参数 nTicks 和 intervalMs 模拟行情变化进行写入行情流表
for (i in 0..(nTicks - 1)) {
// CNY_FR_007 (FR007 浮动利率)
quotes = quotes007 + (i % 10) * 0.00001 // 每个价格微调,模拟行情变化
for (j in 0..(instTypes.size() - 1)) {
tableInsert(streamQuoteTb, now(true), instTypes[j], "", instNames[j], terms[j], quotes[j])
sleep(intervalMs)
}
// FxSwap Point (USDCNY 外汇掉期点)
swapQuotes = fxSwapPoints + (i % 10) * 0.000001 // 每个价格微调,模拟行情变化
for (j in 0..(fxSwapTerms.size() - 1)) {
tableInsert(streamQuoteTb, now(true), "FxSwap", "", "USDCNY", fxSwapTerms[j], swapQuotes[j])
sleep(intervalMs)
}
// Volatility Surface Price (USDCNY 外汇期权的波动率报价)
vsQuotes = fxVsQuotes + (i % 10) * 0.000001 // 每个价格微调,模拟行情变化
for (j in 0..(fxVsTerms.size() - 1)) {
for (p in 0..(fxVsNames.size() - 1)) {
tableInsert(streamQuoteTb, now(true), "FxOption", fxVsNames[p], "USDCNY", fxVsTerms[j], vsQuotes[j, p])
sleep(intervalMs)
}
}
// FxSpot (USDCNY 即期汇率)
spotQuote = 7.1627 + (i % 10) * 0.0001 // 每个价格微调,模拟行情变化
tableInsert(streamQuoteTb, now(true), "FxSpot", "", "USDCNY", 0d, spotQuote)
}
}
// 启动行情播放任务,驱动市场数据构建和期权定价。
nTicks = 100 // 模拟行情数量
intervalMs = 10 // 模拟逐笔行情流间隔(毫秒)
submitJob("replayQuotesJob", "replayQuotesJob", mockQuotes, nTicks, intervalMs)
四类行情到齐后,市场数据引擎依次重建三层市场数据,定价引擎随后输出外汇远期与期权的 NPV 及 Greeks。
5.3 运行结果
在上述 5.2 章节代码执行完成后,系统会实时将曲线构建结果和外汇远期、外汇欧式期权工具定价结果输出至 curveResult 和 pricingResult 流表中(注:非级联模式下才会有 curveResult 数据)。
外汇远期、欧式期权定价结果
通过查询定价结果流表,可以获取外汇远期、欧式期权的 NPV 及 欧式期权 Greeks 指标:
select * from pricingResult order by sysTime
查询结果数据示例如图 5-2 所示:
本案例中 步骤② 配置参数中指定欧式期权定价时需额外输出 Greeks 指标,所以图 5-2 中,欧式期权定价结果中额外包含了Greeks 指标数据。
定价结果数据字段说明:
-
name:待定价外汇远期、外汇欧式期权工具的代码(本场景中均为模拟)。
-
date:估值日期,等于 referenceDate。
-
npv:外汇远期/欧式期权的净现值(NPV)。
-
Greeks 指标:delta、gamma、vega、theta、rhoDomestic、rhoForeign 。
-
eventTime:触发本次定价的事件时间戳。
-
sysTime:定价结果记录写入的系统时间戳。
-
delayStart:每笔定价对应触发的原始行情时间戳(性能观测用)。
曲线/曲面构建结果
非级联模式下,市场数据引擎每次构建市场数据输出时,回调函数 mktDataHandler 会将构建结果写入 curveResult 流表。
select eventTime, kind, date, name, data, sysTime from curveResult
市场数据查询结果如图 5-3 所示:
结果字段说明:
-
eventTime:触发构建该市场数据的行情时间戳,由市场数据引擎运行配置 mktEngCfg 的 useSystemTime、 timeColumn 参数配置确定是否使用系统时间还是行情时间。
-
kind:标识市场数据类型,当前场景包括:Curve(利率互换收益率曲线数据)、Surface(波动率曲面数据)、Price (价格数据,本场景中为USDCNY即期汇率价格)。
-
date:参考日期,等于 referenceDate。
-
name:曲线名称,由曲线构建配置 curveCfg 中的 name 参数决定。
-
data:曲线数据,为 MKTDATA 类型,可以通过
extractMktData解析查看,可参考 2.4 市场数据定义 章节中市场数据提取的内容。 -
sysTime:市场数据记录写入时的系统时间戳。
5.4 场景要点小结
-
三层市场数据存在依赖链:波动率曲面依赖两条折现曲线、跨币种曲线依赖单货币曲线。配置中引用的曲线名(discountCurve/domesticCurve/foreignCurve)必须与被依赖曲线的 name 一致;
-
FxSpot 即期行情数据通过 type 类型为 FxSpot 注入行情流表;
-
开启 Greeks 后,定价引擎自定义回调函数
pricingHandler参数个数与结果流表列数必须同步扩展。
6. 性能
本章对市场数据引擎与定价引擎在债券金融资产场景,不同行情频率及持仓规模下的性能表现进行实测与评估。测试重点关注曲线构建耗时、金融工具批量重定价耗时以及全链路端到端时延(End-to-End Latency)。
6.1 性能测试概览
测试环境:DolphinDB 单节点环境,环境配置如表 6-1 所示。
表 6-1 测试环境配置表
| 配置项 | 信息 |
|---|---|
| OS(操作系统) | CentOS Linux 7 (Core) |
| 内核 | 3.10.0-1160.el7.x86_64 |
| CPU | Intel(R) Xeon(R) Gold 5220R CPU @ 2.20GHz |
| 内存 | 256 GB |
| DolphinDB 版本 | v3.00.6.1 |
不同行情频率、不同定价工具数量场景下从行情输入 → 曲线构建 → 定价输出的端到端的时延,以及曲线构建、定价两个阶段的各自耗时性能,如下表 6-2 所示。
表 6-2 债券场景曲线构建+定价性能(单线程)
| 业务场景 | 债券数量 | 平均总耗时 (ms) | 其中:定价耗时 (ms) | 其中:曲线构建耗时 (ms) | 性能表现评价 |
|---|---|---|---|---|---|
| 极速行情(低负载) | 100 支 | 3.31 | 1.36 | 1.94 | 极低延迟:适用于对单支或少量债券的实时监控 |
| 标准业务(中负载) | 10,000 支 | 54.40 | 51.34 | 3.05 | 表现平稳:耗时随合约量线性增长,无额外损耗 |
| 高并发(高负载) | 100,000 支 | 361.86 | 358.81 | 3.05 | 单核极限:单线程处理 10 万支券仅需约 0.36 秒 |
6.2 性能结论
-
极速响应:处理 100 支债券的实时定价时总耗时仅约 3.3 毫秒,完全满足高频交易和实时风控需求。
-
线性扩展性:耗时与定价合约数量高度正相关(线性增长),业务规模扩大时系统性能可预测、稳定。
-
级联效率高:曲线构建引擎与定价引擎级联工作时,曲线构建本身开销极低(通常 3 毫秒左右),核心耗时集中在复杂的定价计算上。
7. 常见问题 (FAQ)
Q1:行情已回放,但 pricingResult 中没有结果?
按顺序排查:
① 行情是否全部到齐(曲线有触发条件,债券场景 14 支券缺一不可,可先查 curveResult 是否有输出);
② 历史定盘是否已注入(IRS 场景缺 PRICE_FR_007 会导致首笔浮动现金流无法确定);
③ 定价工具依赖的曲线名与 mktDataConfig 中的 name 是否一致(如外汇波动率曲面的 domesticCurve/foreignCurve);
④ 订阅是否正常(unsubscribe 后未重新 subscribe)。
Q2:曲线构建报错或曲线点缺失?
检查行情报价是否为小数形式(3.135% 应为 0.03135,注意示例代码中的 \ 100)、type/name/term 三元组是否与曲线 insts 配置完全对应、期限拼写是否一致(如 "1M" 与 "1m" 不等价)。
Q3:IRS 定价结果与预期偏差较大?
重点核查历史定盘:FR_007 定盘数据是否覆盖定价日前至少 70 个交易日、日期是否为交易日序列、数值是否为小数形式。定盘缺失或错误时,浮动端现金流将使用错误的重置利率。
Q4:如何查看引擎构建出的曲线内容?
非级联模式下从 curveResult 流表取 data 列,使用 extractMktData(data)
解析为可读内容;级联模式下建议临时切换为非级联模式以便留存观察。
Q5:生产环境如何接入真实行情?
将 mockQuotes 替换为真实行情接入链路(行情插件订阅或 replayDS
回放历史数据),写入 marketQuotes 流表即可,引擎侧代码不变。综合版完整代码 ficcMktDataEnginePricingEngineDemo.dos
展示了 getEnvConfig 环境配置、多流表分流订阅、replayDS
历史回放等生产化模式,可作为参考。
8. 附件与配套文件清单
本教程各场景涉及的代码文件和数据文件如下:
表 8-1 代码、数据文件清单
| 文件 | 说明 |
|---|---|
| Bond_PricingDemo.dos | 债券场景完整实例:基准券加载、国债收益率曲线构建、债券实时定价 |
| IRS_PricingDemo.dos | IRS 场景完整实例:FR_007 曲线构建、历史定盘注入、利率互换实时定价 |
| Fx_PricingDemo.dos | 外汇场景完整实例:三层市场数据构建、外汇远期/期权定价与 Greeks |
| ficcMktDataEnginePricingEngineDemo.dos | 综合版完整代码(多场景一体化,含生产化模式参考) |
| data.zip | 模拟行情数据与金融工具基础数据(bond / Inst / FxOptionQuotes) |
| loadData.dos | 附件数据加载脚本 |
配套文档:
-
市场数据引擎接口:DolphinDB 官方文档 createMktDataEngine
-
定价引擎接口:DolphinDB 官方文档 createPricingEngine
-
曲线构建参数:irSingleCurrencyCurveBuilder、irCrossCurrencyCurveBuilder、fxVolatilitySurfaceBuilder
