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 分别获取,避免手工近似。
二、方法流程:从债券条款到 QuantLib 定价对象
在 QuantLib 中构建固定利率债券,核心是“先描述条款,再绑定曲线,最后取价格”。典型流程如下:
- 确定估值日与结算日,设置
Settings::instance().evaluationDate。 - 定义付息频率、计息基准、日历与滚动规则,构造
Schedule。 - 用
FixedRateBond绑定面值、票息、起息日、到期日与 Schedule。 - 构建或传入
YieldTermStructure(贴现曲线)。 - 通过
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 对应 |
|---|---|---|
| 面值 | 100 | FixedRateBond 参数 face |
| 票息率 | 3.2% 年化 | coupons 列表 |
| 付息频率 | 半年一次 | ql.Semiannual |
| 计息基准 | ActualActual ISDA | day_count |
| 应计利息 | ≈ 0.80 | BondFunctions.accruedAmount |
| 全价 | ≈ 101.35 | bond.dirtyPrice() |
| 净价 | ≈ 100.55 | bond.cleanPrice() |
四、误区与复盘:应计利息和日期规则最容易踩坑
- 把净价当全价贴现:贴现现金流得到的是全价,直接与市场净价比较会系统性偏差一个应计利息。
- 忽略结算日与估值日差异:QuantLib 中结算日由 settlementDays 推导,估值日改变会直接影响应计天数与贴现期限。
- 日历与滚动规则不一致:Schedule 使用 Unadjusted 还是 ModifiedFollowing,会改变付息日,从而改变现金流日期与应计区间。
- 计息基准混用:票息计息与曲线贴现可使用不同 day count,需分别设置,不能想当然统一。
dirtyPrice、accruedAmount、cleanPrice 与 settlementDate,并核对 dirty − accrued ≈ clean。若三者关系不成立,优先检查 Schedule 与 day count,而不是怀疑曲线。掌握这条链路后,后续曲线构建、久期与凸性计算、情景分析都可以在同一套债券对象上展开。关键是把“条款—日期—现金流—价格口径”四者对齐,让 QuantLib 负责精确计算,你负责验证口径。