- 金融科技
- 科学计算
【免费下载链接】tf-quant-finance
High-performance TensorFlow library for quantitative finance.
CurveBuilderResult 是 tf-quant-finance 中 Hagan-West 债券贴现曲线校准的"标准返回容器",由bond_curve()函数在完成迭代拟合后统一打包曲线时点、贴现率、贴现因子、初始猜测与收敛诊断信息。本文以 CurveBuilderResult.md 为骨架,结合 bond_curve.py 源码与 bond_curve_test.py 测试,逐字段讲解其语义、生成逻辑与实战用法,读完你即可独立完成"从四只附息债券市场价格反解整条贴现曲线"的完整流程,并能正确解读converged/failed等诊断标志。
CurveBuilderResult 是什么:Hagan-West 曲线校准的结果容器
在 tf-quant-finance 的rates.hagan_west模块中,bond_curve()负责基于(可能带票息的)债券市场价格构建贴现率曲线(discount curve),其返回值就是一个CurveBuilderResult实例。源码在 bond_curve.py 中以@utils.dataclass装饰器定义了该类,官方文档将其描述为 "Bond curve calibration results"(债券曲线校准结果)。
tf_quant_finance.rates.hagan_west.CurveBuilderResult( times, discount_rates, discount_factors, initial_discount_rates, converged, failed, iterations )七个字段按固定顺序排列,均可按位置传参构造。类本身不做任何数值计算,它只是bond_curve()返回的纯数据容器;真正的拟合逻辑发生在bond_curve()内部,最终在函数末尾将各中间结果打包进该实例返回(见 bond_curve.py)。
CurveBuilderResult同时也是模块公开 API 的一部分:在 hagan_west/init.py 中,bond_curve与CurveBuilderResult被一并导出,因此既可以直接from tf_quant_finance.rates.hagan_west import CurveBuilderResult引用,也可以直接使用bond_curve()的返回结果而无需手动导入。
七个字段的完整语义
文档给出的属性说明与源码中的类型注解完全一致,字段类型分别对应types.RealTensor、types.BoolTensor与types.IntTensor(见 data_types.py 中的类型别名):
| 字段 | 形状 | 类型 | 语义 |
|---|---|---|---|
times | Rank 1 | 实数Tensor | 计算得到的贴现率所对应的时点 |
discount_rates | Rank 1 | 与times同 dtype | 推断出的贴现率 |
discount_factors | Rank 1 | 与times同 dtype | 推断出的贴现因子 |
initial_discount_rates | Rank 1 | 与times同 dtype | 贴现率的初始猜测 |
converged | 标量 | 布尔Tensor | 过程是否收敛 |
failed | 标量 | 布尔Tensor | 过程是否失败 |
iterations | 标量 | int32Tensor | 实际执行的迭代次数 |
各字段的深层语义如下:
times:即所有债券的到期时点(expiry times)。文档与源码均明确说明,这些时点"被选取为所提供现金流的到期时间"(见 bond_curve.py 的返回说明与expiry_times = bond_cashflow_times[i][-1]的取值逻辑)。换言之,结果曲线的"节点"不是用户任意指定的网格,而是输入债券的到期日。discount_rates:在到期时点上的贴现率,由discount_factors反解得到:discount_rates = -tf.math.log(discount_factors) / expiry_times(bond_curve.py)。结合discount_factor = exp(-rate * time)的定价公式可推断,这里的利率是连续复利年化利率(十进制小数形式,如0.05表示 5%)。discount_factors:对应到期时点的贴现因子,是迭代过程的核心状态变量——每一轮迭代都在更新"到期贴现因子"向量,收敛判定也是针对它进行的(见下文诊断部分)。initial_discount_rates:实际采用的初始猜测。若调用方未提供,bond_curve()会用各债券的到期收益率(yields to maturity,YTM)作为初值(源码路径:_initial_discount_rates→cashflows.yields_from_pv,见 bond_curve.py 与 bond_curve.py)。注意结果中返回的是"实际使用"的初值,便于复现实验。converged:布尔标量,指示过程是否收敛。收敛判据是"相邻两次迭代中,到期贴现因子的最大绝对变化小于discount_tolerance"。failed:布尔标量,指示过程是否失败。当贴现率或贴现因子出现 NaN 时置为 True。iterations:int32 标量,即实际执行的迭代次数,受maximum_iterations上限约束。
bond_curve() 如何生成这些结果:底层迭代流程
理解结果字段,关键在于理解bond_curve()的迭代拟合算法。该算法基于 Hagan 与 West 提出的 Monotone Convex 插值法(见 README.md 与参考文献 [1][2]),整体思路是:假设一组到期贴现率 → 用 Monotone Convex 插值得到所有现金流时点的贴现率 → 通过债券定价公式反解各债券的到期贴现率 → 用反解结果更新曲线 → 重复直至收敛。
源码 bond_curve.py 中的_build_discount_curve将这一过程形式化为:
- 初始化:取初始贴现率/贴现因子作为当前收益率曲线。
- 插值:从当前曲线插值出所有债券现金流时点的贴现率。
- 反解到期贴现率:已知债券价格满足
P e^{-r_0 t_0} = c_1 e^{-r_1 t_1} + ... + c_n e^{-r_n t_n} (A)在已知
r_0, r_1, ..., r_{n-1}后,可反解出r_n。实现上改写为(z_i = e^{-r_i t_i}):-c_n z_n = -P z_0 + c_1 z_1 + c_2 z_2 + ... + c_{n-1} z_{n-1} (B)即每只债券的 RHS 等价于一组"合成现金流"
[-P, c_1, ..., c_{n-1}]在时点[t_0, t_1, ..., t_{n-1}]上的现值,随后通过Zns = -SegmentSum(Cs * Zs) / Cns与Rns = -log(Zns) / Tns得到下一轮猜测(源码见 bond_curve.py)。 - 重复:把新猜测作为下一轮曲线,直到收敛或达到
maximum_iterations。
从实现细节看:
- 迭代以
tf.while_loop执行,循环变量初始值为(False, False, 0, initial_discount_factors),其中initial_discount_factors = exp(-initial_discount_rates * expiry_times)(bond_curve.py)。 - 每轮迭代中,插值调用
monotone_convex.interpolate_yields,将当前到期贴现率插值到所有现金流时点(bond_curve.py)。 - 迭代结束后,
times、discount_rates、discount_factors、initial_discount_rates、converged、failed、iterations七个字段被一次性打包返回(bond_curve.py)。
其中 Monotone Convex 插值本身由 monotone_convex.py 提供:它不是求解标准插值问题,而是寻找满足Integral[f(u), t_{i-1} <= u <= t_i] = f_i的函数f(t)(即瞬时远期利率),再通过r(t) = (1/t) * Integrate[f(s), 0 <= s <= t]积分得到收益率曲线(见 monotone_convex.py 中interpolate_yields的说明与示例)。
完整实战:四只附息债券拟合贴现曲线
下面完整复现 bond_curve.py 文档字符串中的官方示例,并补充numpy导入使其可直接运行。示例用四只不同到期期限的附息债券反解贴现曲线:
import numpy as np import tensorflow as tf import tf_quant_finance as tff dtype = np.float64 # 各债券的现金流时点(必须按到期时间升序排列) cashflow_times = [ np.array([0.25, 0.5, 0.75, 1.0], dtype=dtype), # 1 年期 np.array([0.5, 1.0, 1.5, 2.0], dtype=dtype), # 2 年期 np.array([0.5, 1.0, 1.5, 2.0, 2.5, 3.0], dtype=dtype), # 3 年期 np.array([0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0], dtype=dtype) # 4 年期 ] cashflows = [ # 1 年期债券,5% 每季度付息 np.array([12.5, 12.5, 12.5, 1012.5], dtype=dtype), # 2 年期债券,6% 每半年付息 np.array([30, 30, 30, 1030], dtype=dtype), # 3 年期债券,8% 每半年付息 np.array([40, 40, 40, 40, 40, 1040], dtype=dtype), # 4 年期债券,3% 每半年付息 np.array([15, 15, 15, 15, 15, 15, 15, 1015], dtype=dtype) ] # 上述现金流的市场现值(即债券的全价 / dirty price) pvs = np.array([ 999.68155223943393, 1022.322872470043, 1093.9894418810143, 934.20885689015677 ], dtype=dtype) results = tff.rates.hagan_west.bond_curve(cashflows, cashflow_times, pvs) # 曲线时点即所提供现金流的到期时间 np.testing.assert_allclose(results.times, [1.0, 2.0, 3.0, 4.0]) expected_discount_rates = np.array([5.0, 4.75, 4.53333333, 4.775], dtype=dtype) / 100 np.testing.assert_allclose(results.discount_rates, expected_discount_rates, atol=1e-6) # 贴现因子由 exp(-rate * time) 给出 expected_discount_factors = np.exp(-expected_discount_rates * [1.0, 2.0, 3.0, 4.0]) np.testing.assert_allclose(results.discount_factors, expected_discount_factors, atol=1e-6) print("times:", results.times.numpy()) print("discount_rates:", results.discount_rates.numpy()) print("discount_factors:", results.discount_factors.numpy()) print("converged:", results.converged.numpy()) print("failed:", results.failed.numpy()) print("iterations:", results.iterations.numpy())运行后各字段的预期行为与 bond_curve_test.py 中test_correctness的断言完全一致:
times为[1.0, 2.0, 3.0, 4.0],即四只债券的到期时点;discount_rates逼近[0.05, 0.0475, 0.0453333333, 0.04775](即 5.0%、4.75%、4.53333333%、4.775%);discount_factors逼近exp(-rates * times);converged为 True、failed为 False。
值得强调的是,结果字段可按属性访问,也可按位置解包。由于CurveBuilderResult是 attrs 数据类且定义了__len__与__iter__,你可以写出times, rates, factors, init_rates, converged, failed, iters = results这样的解包代码。
作为 tf.function 兼容数据类:attrs 与iter/len/eq方法
文档末尾列出的__eq__、__ge__、__gt__、__le__、__lt__、__ne__方法均由 attrs 库自动生成,用于实例比较;而__iter__与__len__则由utils.dataclass装饰器注入,其实现位于 dataclass.py:
def __iter__(self): # 按类定义中字段的固定顺序迭代属性值 for item in self.__attrs_attrs__: yield getattr(self, item.name) def __len__(self): return len(self.__attrs_attrs__)这套机制并非锦上添花,而是与 tf.function 深度协作的基础。utils.dataclass装饰器(dataclass.py)会把类包装为 attrs 类(attr.s(cls, auto_attribs=True)),从而让实例可以作为tf.function的输入/输出,也可以作为tf.while_loop的循环变量(loop variable)传递。这正是CurveBuilderResult能在图执行模式下被安全返回、并且可被self.evaluate()(TF2 测试框架)直接求值的原因——测试代码中大量使用results.converged、results.discount_rates等属性断言即是证据(见 bond_curve_test.py)。
如何通过结果字段诊断校准质量
converged与failed两个标量字段是最重要的"体检指标",其判定逻辑在 bond_curve.py 的one_step中:
failed = tf.math.reduce_any( tf.math.is_nan(expiry_rates) | tf.math.is_nan(expiry_discounts)) ... discount_diff = tf.math.abs(next_expiry_discounts - expiry_discounts) converged = (~tf.math.reduce_any(tf.math.is_nan(discount_diff)) & (tf.math.reduce_max(discount_diff) < discount_tolerance))即:
converged == True:相邻两轮迭代中,各到期贴现因子的最大绝对变化小于discount_tolerance(默认1e-8),且没有 NaN;failed == True:某轮迭代中贴现率或贴现因子出现 NaN,通常意味着迭代发散;- 两者可同时为 False:达到
maximum_iterations(默认 50)上限而提前终止,此时既未收敛也未报失败。
测试用例直观展示了如何用这两个字段发现异常:
- 极端利率下的发散:
test_unstable(bond_curve_test.py)构造贴现率放大 100 倍的极端价格,默认初值下结果为converged=False, failed=True,且部分discount_rates为 NaN;即使把初值设成真实值的 0.9999 倍(仅低估 1 个基点),过程依然失败——这正是文档中"稳定性"局限的实证。 - 非凸性导致多解:
test_non_convex(bond_curve_test.py)用真实值 1.01 倍作初值时过程收敛,但收敛到完全不同的利率组合[4.96098643, 4.17592063, 2.83970042, 2.38685078](单位为百分数),且这些利率对债券的定价同样正确。这说明拟合目标函数非凸,不同初值会落入不同极小值,解读结果时必须结合initial_discount_rates字段追溯初值。
除诊断字段外,以下三个入参直接影响结果质量:
discount_tolerance(默认1e-8):收敛阈值,越小越严格,可能增加iterations;maximum_iterations(默认50):迭代上限,防止死循环,但太小可能导致未收敛;initial_discount_rates(默认None→ 自动取 YTM):如前所述,由于非凸性,初值选择可能改变最终解。
已知局限与使用注意事项
文档与源码共同指出了若干必须注意的边界条件:
- 非凸性:拟合所隐含的损失函数非凸,同一价格数据在给定容差下可能存在多组贴现率解,最终收敛到哪个极小值取决于初值(bond_curve.py)。
- 稳定性:若初值偏差过大或利率人为偏高,某步迭代估计出的贴现因子可能为负,导致无法继续迭代出实数贴现率;从数学上也可证明,当最终现金流不大于所有中间现金流时过程会发散(bond_curve.py)。
- 输入约束:至少需要两只债券;各债券现金流时点必须严格递增;首笔现金流必须晚于交割时点;最终现金流必须是该债券最大的一笔。设置
validate_args=True时这些条件会以tf.debugging断言强制校验,违反时抛出tf.errors.InvalidArgumentError(bond_curve.py);即使不开启,长度一致性等静态检查也始终执行,违规会抛出ValueError(bond_curve.py)。 - 当前实现范围:模块目前仅支持从债券构建曲线,swap 曲线构建尚未实现(见 README.md 中的 TODO 标注)。
相关文件与延伸阅读
- 结果容器定义与
bond_curve()完整实现:bond_curve.py - 数据类装饰器(
__iter__/__len__的来源):dataclass.py - Monotone Convex 插值实现与示例:monotone_convex.py
- 收敛、失败、非凸、负利率等场景的完整测试:bond_curve_test.py
- 模块算法总览与参考文献:README.md
- 官方 API 参考:CurveBuilderResult.md、monotone_convex.md
需要说明的是,bond_curve()各参数(present_values_settlement_times默认按 0 处理、dtype默认由 TensorFlow 推断为 float32 等)的完整取值说明可进一步查阅 bond_curve.py 的 Args 文档,而 CurveBuilderResult 本身作为返回容器,掌握其七个字段的含义与生成时机,即掌握了整个 Hagan-West 曲线校准流程的"结果解读入口"。
- 金融科技
- 科学计算
【免费下载链接】tf-quant-finance
High-performance TensorFlow library for quantitative finance.
相关推荐
tf-quant-finance 利率曲线构建实战:Hagan West 算法与 Monotone Convex 插值完全指南
tf quant finance 利率曲线构建实战:Hagan West 算法与 Monotone Convex 插值完全指南 本文围绕 tf quant fi
金融科技科学计算tf-quant-finance 利率曲线插值实战:深入解析 `interpolate_yields` 与 Hagan–West 单调凸插值
tf quant finance 利率曲线插值实战:深入解析 interpolate_yields 与 Hagan–West 单调凸插值 导读 本文聚焦 Goo
金融科技科学计算F2 核心概念解析:掌握图表结构、声明式语法与视觉编码(Chart Architecture & Grammar)
F2 核心概念解析:掌握图表结构、声明式语法与视觉编码(Chart Architecture & Grammar) 导读 F2 是一款面向移动端的声明式图表库,
金融科技科学计算
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考