- 金融科技
- 科学计算
【免费下载链接】tf-quant-finance
High-performance TensorFlow library for quantitative finance.
本指南围绕 tf-quant-finance 中tf_quant_finance.rates.hagan_west.bond_curve展开,系统讲解如何利用 Hagan-West 算法与 Monotone Convex(单调凸)插值方案,从一组带息债券的市场价格中自举(bootstrap)出贴现率曲线。读完本文,你将掌握该 API 的全部参数语义、底层迭代算法原理、完整可运行的代码示例、返回结果字段的含义,以及算法的非凸性与稳定性局限,并能在实际利率曲线构建任务中直接落地使用。
一、背景:贴现曲线与债券市场数据
贴现曲线(discount curve)是时间t的函数,表示今天存入一个单位货币、存期为t时适用的利率。债券的交易价格隐含着市场对贴现率的看法,贴现曲线构建的目的正是从债券价格中提取这一信息。在 tf-quant-finance 中,bond_curve支持从(可能带息)债券数据构建利率曲线;除了债券,市场数据还可来自互换(swap)、远期利率协议(FRA)或欧洲美元期货,但当前实现仅支持债券曲线构建(参见 hagan_west/README.md 中关于 swap 曲线构建待支持的计划)。
假设有N只到期日递增的债券B_i,其市场价格已知;第i只债券在时刻T_{ij}(1 <= j <= n_i,n_i为该债券含到期日在内的现金流数量)产生现金流,最终支付时刻记为T_i = T_{i,n_i}。bond_curve估计一组贴现率r(T_i),使得这些利率通过 Monotone Convex 插值方案插值到其他所有现金流时刻后,计算出的债券价值与市场价值一致(在给定容差范围内)。该算法基于 Hagan 与 West 提出的 Monotone Convex Interpolation 方法(参考文献 [1]、[2],详见原文档与 hagan_west/README.md)。
二、API 签名与全部参数详解
函数完整签名如下(与文档一致,见 bond_curve.md):
tf_quant_finance.rates.hagan_west.bond_curve( bond_cashflows, bond_cashflow_times, present_values, present_values_settlement_times=None, initial_discount_rates=None, discount_tolerance=1e-08, maximum_iterations=50, validate_args=False, dtype=None, name=None )各参数语义如下:
| 参数 | 类型与约束 | 说明 | 默认值 |
|---|---|---|---|
bond_cashflows | Tensor列表 | 每个Tensor为一维、相同实数 dtype,长度可不同;每个元素代表一只债券的现金流序列,列表整体即用于构建曲线的债券集合 | 必填 |
bond_cashflow_times | Tensor列表 | 长度必须与bond_cashflows相同,且与同下标现金流一一对应;每个Tensor为一维、与现金流同 dtype,元素严格为正且严格递增,各债券的现金流时刻按升序排列 | 必填 |
present_values | 标量Tensor列表 | 与bond_cashflows等长、同 dtype;债券现金流的市场价格,即全价(all-in / dirty price) | 必填 |
present_values_settlement_times | 标量Tensor列表 | 与债券等长、同 dtype;各债券自交易时至购买价格实际交割时刻的结算时间;不提供时视为每只债券结算时间为 0 | None |
initial_discount_rates | Tensor | 与present_values同 dtype、同形状;迭代过程的贴现率初始猜测 | None,此时使用债券到期收益率(yield to maturity)作为初始值 |
discount_tolerance | 正标量Tensor | 拟合迭代终止的绝对容差:当债券到期时刻的估计贴现因子在一次迭代间的变化量小于该值时停止迭代 | 1e-8 |
maximum_iterations | 正整数Tensor | 拟合曲线允许的最大迭代次数 | 50 |
validate_args | 布尔 | 是否启用输入参数校验(校验项详见下文) | False |
dtype | tf.Dtype | 指定bond_cashflows、bond_cashflow_times与present_values的 dtype | None,由 TensorFlow 推断(float32) |
name | str | 该函数创建 ops 的名称 | None,映射为'hagan_west' |
其中bond_cashflows与bond_cashflow_times在实现中通过_convert_to_tensors逐项转换为tf.Tensor(见 bond_curve.py);present_values_settlement_times缺省时会被替换为每个现值的零张量(bond_curve.py)。
三、算法核心:递归迭代求解贴现率
bond_curve的实现位于 bond_curve.py 的_build_discount_curve(L321-L482),是一个递归过程,每一步如下:
- 设定初始贴现率/贴现因子,作为当前收益率曲线。
- 从当前曲线插值,得到所有债券现金流发生时刻的贴现率。
- 反解债券定价公式,计算每只债券到期时刻的贴现率。设
P为现值、r_i为到t_i时刻的贴现率、c_i为t_i时刻的现金流,则债券价格满足:
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,改写为适合实现的形式:
-c_n z_n = -P z_0 + c_1 z_1 + c_2 z_2 + ... + c_{n-1} z_{n-1} (B)其中贴现因子为:
z_i = e^{-r_i t_i} (C)式 (B) 的右端形如现金流[-P, c_1, c_2, ..., c_{n-1}]在时刻[t_0, t_1, ..., t_{n-1}]的现值。实现中按如下方式批量计算(L426-L459):
- 拼接"合成"现金流时刻:
Ts = [t1_0, ..., t1_{n1-1}] + [t2_0, ..., t2_{n2-1}] ... - 拼接合成现金流:
Cs = [-P1, c1_1, ..., c1_{n1-1}] + [-P2, c2_1, ..., c2_{n2-1}] ... - 计算
Rs = InterpolateRates[Ts],Zs = exp(-Rs * Ts) - 记
Zns = [z_n1, z_n2, ...]、Cns = [c1_n, c2_n, ...]为各债券到期贴现因子与期末现金流,则Zns = - SegmentSum(Cs * Zs) / Cns - 由
Rns = -log(Zns) / Tns得到下一轮贴现率猜测,回到步骤 1 继续迭代直至收敛
迭代通过tf.while_loop实现,maximum_iterations直接作为循环上限参数传入(L470-L471);收敛判定为各到期贴现因子相邻迭代最大绝对差小于discount_tolerance,converged标志同时要求差值不存在 NaN(L456-L458)。
初始猜测:到期收益率
当initial_discount_rates未提供时,实现调用_initial_discount_rates(L485-L502),底层复用 cashflows.py 的yields_from_pv,以每只债券的到期收益率(内部收益率)作为初始贴现率。源码注释特别提醒:此计算会忽略结算时间——只要结算时间相对债券到期日不大,忽略结算时间相当于在真实贴现曲线为正时高估起始点,这通常是可接受的近似(L303-L312)。
四、完整示例:从四只带息债券构建隐含曲线
以下示例完整继承自 API 文档,用于从四只不同期限的带息债券构建贴现率曲线,可直接复制运行:
import numpy as np from tf_quant_finance.rates.hagan_west import bond_curve dtype = np.float64 # 注意:必须按到期时间升序排列 cashflow_times = [ np.array([0.25, 0.5, 0.75, 1.0], dtype=dtype), np.array([0.5, 1.0, 1.5, 2.0], dtype=dtype), np.array([0.5, 1.0, 1.5, 2.0, 2.5, 3.0], dtype=dtype), np.array([0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0], dtype=dtype) ] 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) ] # 上述现金流的现值(市场全价) pvs = np.array([ 999.68155223943393, 1022.322872470043, 1093.9894418810143, 934.20885689015677 ], dtype=dtype) results = 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)示例中 1 年期债券每季度现金流为 12.5(票面 1000 的 5% 年利率按季度支付),期末含本金为 1012.5;依此类推。该示例同时是 bond_curve_test.py 中test_correctness的验证基准:测试在 float32 与 float64 两种精度下断言times == [1.0, 2.0, 3.0, 4.0]、贴现率与预期值误差不超过1e-6,并校验converged为真、failed为假。
五、返回结果:CurveBuilderResult 字段
函数返回一个CurveBuilderResult实例(定义于 bond_curve.py,字段文档见 CurveBuilderResult.md),包含以下属性:
| 字段 | 类型 | 含义 |
|---|---|---|
times | 一维实数Tensor | 计算得到的贴现率对应时刻,取所提供现金流的到期时间 |
discount_rates | 一维Tensor | 推断出的贴现率,与times同 dtype |
discount_factors | 一维Tensor | 推断出的贴现因子,与times同 dtype |
initial_discount_rates | 一维Tensor | 贴现率的初始猜测 |
converged | 标量布尔Tensor | 是否收敛:各到期贴现因子相邻迭代的最大绝对差低于discount_tolerance即视为收敛 |
failed | 标量布尔Tensor | 是否失败:贴现率或贴现因子出现 NaN 即视为失败 |
iterations | 标量 int32Tensor | 实际执行的迭代次数 |
其中贴现率与贴现因子在实现末尾统一由discount_factors换算:discount_rates = -log(discount_factors) / expiry_times(L472-L473)。
六、参数校验:静态校验与运行时断言
静态校验(始终执行)
_perform_static_validation(L505-L532)在 TensorFlow 图构建前即完成以下检查,不满足时抛出ValueError:
bond_cashflows与bond_cashflow_times长度不一致;bond_cashflows与present_values长度不一致;present_values与present_values_settlement_times长度不一致;- 债券数量少于 2("At least two bonds must be supplied to calibrate the curve.")。
运行时断言(validate_args=True时)
_validate_args_control_deps(L535-L553)为每只债券生成三类断言,违反时抛出tf.errors.InvalidArgumentError:
- 不存在早于或等于相应结算时间(未提供结算时间则为时刻 0)到期的现金流;
- 每只债券的现金流时刻构成严格递增序列;
- 每只债券的期末现金流大于该债券的其他任何现金流。
测试 bond_curve_test.py 分别以 float32/float64 验证了上述三类错误路径均会抛出InvalidArgumentError。需要注意的是,即使validate_args=False,静态校验仍然执行,因此两个列表长度不匹配等结构性错误始终会被拦截。
七、算法局限:非凸性与稳定性
Hagan-West 拟合算法存在两点值得牢记的局限(详见 API 文档与源码 docstring,bond_curve.py):
非凸性(Non-convexity):过程隐式最小化的损失函数是非凸的。实际含义是:在给定容差下,可能存在多组不同的贴现率都能把现金流定价到容差以内;取决于初始值选择,Hagan-West 过程可能收敛到不同的极小值。测试 test_non_convex 演示了这一点:以真实贴现率(约为 500%、475% 量级的极端场景)的 1.01 倍为初始值,过程同样收敛(
converged=True、failed=False),但收敛到一组完全不同的贴现率[4.96098643, 4.17592063, 2.83970042, 2.38685078],且用这组利率重新计算债券价格仍与市场价在rtol=1e-5内一致——说明多个解都能定价正确。稳定性(Stability):过程迭代时,根据近似债券票息日利率计算到期利率。若初始猜测偏差过大,或即使偏差不大但利率被人为放大,迭代某步估计出的贴现因子可能为负(见参考文献 [2] 中的 Eq. 14),从而无法继续迭代求出实数贴现率。此外可以证明:若期末现金流不大于所有中间现金流,过程将发散。这一情况在债券现金流中不会出现(期末含本金必然是最大),但从数学角度仍是重要考虑。测试 test_unstable 给出了实证:当贴现率被放大 100 倍(真实值
[5.0, 4.75, 4.53333333, 4.775]变为约 500% 量级)时,默认初始值下 100 次迭代仍失败,且第 2 个贴现率为 NaN;即使以真实值的 0.9999 倍(低估)作为初始值也会失败,而高估初始值则可能成功——这直接验证了"初始猜测方向影响收敛结果"的稳定性特征。
因此,实践中建议:优先使用默认的到期收益率初始猜测;当遇到failed=True或未收敛时,可尝试不同initial_discount_rates或增大maximum_iterations与discount_tolerance的组合。
八、底层插值:Monotone Convex 方案
bond_curve的每次迭代都依赖 monotone_convex.py 提供的interpolate_yields(L389-L523)对到期贴现率进行插值。Monotone Convex 方法解决的不是标准插值问题,而是一个修正问题:给定严格递增的时间序列[t_1, ..., t_n]与值[f_1, ..., f_n],寻找定义在[0, t_n]上的函数f(t),满足
Integral[f(u), t_{i-1} <= u <= t_i] = f_i, 其中 t_0 = 0在利率曲线构建语境中,f(t)是时刻t的瞬时远期利率,f_i是作用于时段[t_{i-1}, t_i]的离散远期利率;远期曲线积分与收益率曲线满足Integral[f(u), 0 <= u <= t] = r(t) * t(monotone_convex.py)。
实现使用二次样条插值,根据边界值g0 = f_left - fd与g1 = f_right - fd的关系将平面划分为 4 个楔形区域(Region 1-4,对应参考文献 [2] 的 Fig. 4),逐区域计算插值值与积分值(L229-L269),并处理g0 = g1 = 0时的数值精度特例。interpolate_yields的完整流程为:先由forward_rates_from_yields将收益率曲线转换为离散远期曲线(见 forwards.py),插值得到瞬时远期曲线后积分反推收益率。当前实现支持沿插值时间批量求值,但不支持同时构建多条曲线。bond_curve迭代中的calc_rates = monotone_convex.interpolate_yields(calc_times, expiry_times, yields=expiry_rates)正是调用该入口(bond_curve.py)。
九、边界场景与扩展用法
测试套件还覆盖了若干值得在生产中借鉴的边界场景:
- 平坦曲线:test_flat_curve 验证了 15% 平坦贴现率曲线可被精确恢复(容差
1e-6)。 - 负利率:test_negative_rates 表明在真实贴现率为
[0.02, 0.01, -0.01, -0.03]时算法仍收敛且不失败。 - 负远期利率:test_negative_forwards 表明贴现率全为正、但隐含远期利率包含负值(
[0.12, 0.06, -0.05, -0.01, 0.02])的曲线同样可精确恢复,6 次迭代收敛。 - 零息债券:
test_zero_coupon_bond(L383-L431)混合了零息与带息债券,并与逐期手算的贴现率对照;test_only_zero_coupon_bonds(L437-L472)则验证全部为零息债券时 1 次迭代即可收敛——此时算法退化为逐期直接反解。
扩展用法上,initial_discount_rates允许调用方注入外部先验(例如上一交易日曲线或分析师估算),从而缓解非凸性带来的多解问题;present_values_settlement_times用于处理 T+2 等交割惯例下的结算时间,缺省按 0 处理。若希望将构建好的曲线进一步用于定价,可将results.discount_rates与results.times传入monotone_convex.interpolate_yields对其他时点进行插值(参见 monotone_convex.md)。
十、小结
tf_quant_finance.rates.hagan_west.bond_curve是一个完整、可直接投入使用的债券贴现曲线构建工具:它基于 Hagan-West 的 Monotone Convex 方法,通过"插值-反解-迭代"的递归流程,从一组带息债券的脏价中自举出到期时点的贴现率,并以CurveBuilderResult返回时刻、贴现率、贴现因子、收敛状态与迭代次数等全部信息。使用时需注意债券按到期升序排列、现金流时刻严格递增、期末现金流为最大等输入约定,并理解其非凸性与极端利率下的稳定性局限。在常规市场利率环境下,默认参数即可获得高精度曲线(示例与测试中贴现率误差均在1e-6量级);在极端场景下,则需借助initial_discount_rates等参数进行调优。
- 金融科技
- 科学计算
【免费下载链接】tf-quant-finance
High-performance TensorFlow library for quantitative finance.
相关推荐
F2 核心概念解析:掌握图表结构、声明式语法与视觉编码(Chart Architecture & Grammar)
F2 核心概念解析:掌握图表结构、声明式语法与视觉编码(Chart Architecture & Grammar) 导读 F2 是一款面向移动端的声明式图表库,
金融科技科学计算Humanizer 流式日期 API 详解:`In.Six` 静态类与“从现在起 6 个单位”的相对时间计算
Humanizer 流式日期 API 详解: In.Six 静态类与“从现在起 6 个单位”的相对时间计算 In.Six 是 Humanizer FluentD
金融科技科学计算tf-quant-finance 利率曲线插值实战:深入解析 `interpolate_yields` 与 Hagan–West 单调凸插值
tf quant finance 利率曲线插值实战:深入解析 interpolate_yields 与 Hagan–West 单调凸插值 导读 本文聚焦 Goo
金融科技科学计算
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考