☰
tf-quant-finance 利率曲线拟合结果详解:CurveBuilderResult 字段语义与 Hagan-West 债券曲线校准实战
2026/10/13 2:42:43 网站建设 项目流程
  • 金融科技
  • 科学计算

【免费下载链接】tf-quant-finance

High-performance TensorFlow library for quantitative finance.

项目地址:https://gitcode.com/gh_mirrors/tf/tf-quant-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 中的类型别名):

字段形状类型语义
timesRank 1实数Tensor计算得到的贴现率所对应的时点
discount_ratesRank 1与times同 dtype推断出的贴现率
discount_factorsRank 1与times同 dtype推断出的贴现因子
initial_discount_ratesRank 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将这一过程形式化为:

  1. 初始化:取初始贴现率/贴现因子作为当前收益率曲线。
  2. 插值:从当前曲线插值出所有债券现金流时点的贴现率。
  3. 反解到期贴现率:已知债券价格满足
    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)。

  4. 重复:把新猜测作为下一轮曲线,直到收敛或达到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):如前所述,由于非凸性,初值选择可能改变最终解。

已知局限与使用注意事项

文档与源码共同指出了若干必须注意的边界条件:

  1. 非凸性:拟合所隐含的损失函数非凸,同一价格数据在给定容差下可能存在多组贴现率解,最终收敛到哪个极小值取决于初值(bond_curve.py)。
  2. 稳定性:若初值偏差过大或利率人为偏高,某步迭代估计出的贴现因子可能为负,导致无法继续迭代出实数贴现率;从数学上也可证明,当最终现金流不大于所有中间现金流时过程会发散(bond_curve.py)。
  3. 输入约束:至少需要两只债券;各债券现金流时点必须严格递增;首笔现金流必须晚于交割时点;最终现金流必须是该债券最大的一笔。设置validate_args=True时这些条件会以tf.debugging断言强制校验,违反时抛出tf.errors.InvalidArgumentError(bond_curve.py);即使不开启,长度一致性等静态检查也始终执行,违规会抛出ValueError(bond_curve.py)。
  4. 当前实现范围:模块目前仅支持从债券构建曲线,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.

项目地址:https://gitcode.com/gh_mirrors/tf/tf-quant-finance
点击查看免费下载

相关推荐

上一篇:高效移动端抢票系统:开源API自动化解决方案实战指南
下一篇:大麦网自动化抢票脚本:如何实现毫秒级响应的高效抢票方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询