4. 债券定价基础

固定利率债券的定价看似只是“贴现求和”,但在真实交易与系统中,真正容易出错的往往不是贴现公式,而是现金流口径、日期规则、净价/全价区分以及应计利息的边界处理。本节围绕 QuantLib 的实现路径,把这条链路拆成可验证的工程步骤。

一、核心原理:债券价格是“未来现金流贴现 + 应计调整”

固定利率债券在存续期内产生两类现金流:一是按票面利率与付息频率定期支付的票息,二是到期日偿还的本金(通常为面值 100)。理论全价(Dirty Price)等于所有未来现金流按对应期限贴现后的现值之和:

DirtyPrice = Σ [ CF_i / (1 + y/f)^(f·t_i) ]

其中 CF_i 为第 i 期现金流,y 为到期收益率,f 为年付息频率,t_i 为从结算日到第 i 期现金流日的年化期限。市场报价通常使用净价(Clean Price),即全价减去应计利息:

CleanPrice = DirtyPrice − AccruedInterest

应计利息是卖方在持有期内已赚取但尚未收到的票息补偿,按上一付息日至结算日的天数比例计算。QuantLib 中通过 BondFunctions::accruedAmount 与 Bond::dirtyPrice 分别获取,避免手工近似。

固定利率债券:现金流时间轴与价格构成 付息日 T₀ 付息日 T₁ 付息日 T₂ 到期日 T₃ 票息 C 票息 C 票息 C + 本金 100 应计利息区间 全价 = 净价 + 应计利息 报价用净价,结算用全价

二、方法流程:从债券条款到 QuantLib 定价对象

在 QuantLib 中构建固定利率债券,核心是“先描述条款,再绑定曲线,最后取价格”。典型流程如下:

  1. 确定估值日与结算日,设置 Settings::instance().evaluationDate。
  2. 定义付息频率、计息基准、日历与滚动规则,构造 Schedule。
  3. 用 FixedRateBond 绑定面值、票息、起息日、到期日与 Schedule。
  4. 构建或传入 YieldTermStructure(贴现曲线)。
  5. 通过 bond.dirtyPrice()、BondFunctions::accruedAmount() 与 cleanPrice() 获取结果。
import QuantLib as ql

ql.Settings.instance().evaluationDate = ql.Date(15, 6, 2024)
calendar = ql.China()
settlement_days = 1
face = 100.0
coupon = 0.032
issue = ql.Date(15, 3, 2023)
maturity = ql.Date(15, 3, 2028)
tenor = ql.Period(ql.Semiannual)
day_count = ql.ActualActual(ql.ActualActual.ISDA)

schedule = ql.Schedule(issue, maturity, tenor, calendar,
                       ql.Unadjusted, ql.Unadjusted,
                       ql.DateGeneration.Backward, False)

bond = ql.FixedRateBond(settlement_days, face, schedule,
                        [coupon], day_count)

curve = ql.FlatForward(0, calendar, ql.QuoteHandle(ql.SimpleQuote(0.03)),
                       ql.ActualActual(ql.ActualActual.ISDA))
engine = ql.DiscountingBondEngine(ql.YieldTermStructureHandle(curve))
bond.setPricingEngine(engine)

dirty = bond.dirtyPrice()
accrued = ql.BondFunctions.accruedAmount(bond)
clean = bond.cleanPrice()
print(dirty, accrued, clean)
若只做报价核对,可先用 ql.BondFunctions.cleanPrice 直接传入收益率与结算日,无需完整曲线引擎,适合快速验证应计利息口径。

三、案例场景:一只半年付息国债的净价与全价

假设面值 100、票息 3.2%、半年付息、结算日为 2024-06-15,上一付息日为 2024-03-15,下一付息日为 2024-09-15。按 ActualActual ISDA 计算,应计天数约 92 天,计息期约 184 天,应计利息约为 100 × 3.2% / 2 × 92/184 ≈ 0.80。若贴现得到全价为 101.35,则净价约为 100.55。

项目数值/说明QuantLib 对应
面值100FixedRateBond 参数 face
票息率3.2% 年化coupons 列表
付息频率半年一次ql.Semiannual
计息基准ActualActual ISDAday_count
应计利息≈ 0.80BondFunctions.accruedAmount
全价≈ 101.35bond.dirtyPrice()
净价≈ 100.55bond.cleanPrice()

四、误区与复盘:应计利息和日期规则最容易踩坑

  • 把净价当全价贴现:贴现现金流得到的是全价,直接与市场净价比较会系统性偏差一个应计利息。
  • 忽略结算日与估值日差异:QuantLib 中结算日由 settlementDays 推导,估值日改变会直接影响应计天数与贴现期限。
  • 日历与滚动规则不一致:Schedule 使用 Unadjusted 还是 ModifiedFollowing,会改变付息日,从而改变现金流日期与应计区间。
  • 计息基准混用:票息计息与曲线贴现可使用不同 day count,需分别设置,不能想当然统一。
复盘建议:每次定价后同时打印 dirtyPrice、accruedAmount、cleanPrice 与 settlementDate,并核对 dirty − accrued ≈ clean。若三者关系不成立,优先检查 Schedule 与 day count,而不是怀疑曲线。

掌握这条链路后,后续曲线构建、久期与凸性计算、情景分析都可以在同一套债券对象上展开。关键是把“条款—日期—现金流—价格口径”四者对齐,让 QuantLib 负责精确计算,你负责验证口径。