使用QuantLib从债券结算日而非估值日提取折现因子

24次阅读

使用QuantLib从债券结算日而非估值日提取折现因子

理解QuantLib中的折现因子与日期约定

金融量化分析中,折现因子(discount factor)是衡量未来现金流当前价值的关键工具。它基于收益率曲线,将未来的金额折算到某个特定的参考日期。在quantlib库中,当从一个已构建的收益率曲线(如yieldtermstructure对象)中提取折现因子时,默认的参考日期是全局的“估值日”(evaluation date)。

对于债券的净现值(Net Present Value, NPV)计算,通常会将所有未来现金流折现到估值日。然而,在实际交易中,债券的结算日(Settlement Date)可能与估值日不同。例如,如果今天(估值日)是星期一,而债券将在星期三结算,那么对于结算后的现金流,我们可能需要将其折现到结算日,而非估值日,以便准确计算其含息价格(Dirty Price)。

QuantLib的curve.discount(date)方法会返回从当前估值日到指定date的折现因子。当我们需要以债券结算日为新的参考点来计算后续现金流的折现因子时,直接使用此方法便不再适用。

挑战:以结算日为参考的折现因子

假设我们已经成功引导(bootstrap)了一个收益率曲线curve。当我们尝试获取从结算日到某个现金流日期的折现因子时,可能会遇到以下困惑:

# 假设 curve 已构建,bond.settlementDate() 和 row['date'] 已定义 # 尝试直接获取从结算日到现金流日期的折现因子(可能不符合预期) # row['DiscFactor (Dirty Price)'] = round(curve.discount(bond.settlementDate(), row['date']), 9)

curve.discount(start_date, end_date)方法在QuantLib中通常用于计算从start_date到end_date的远期折现因子,即在start_date已知的情况下,将end_date的金额折现到start_date的因子。这与我们希望将所有现金流都“折回到”settlementDate作为基准点的需求略有不同。我们真正需要的是一个以settlementDate为“零时刻”的折现因子,即DF(settlementDate, cashflowDate)。

解决方案:基于估值日折现因子的调整

解决这个问题的关键在于利用折现因子的乘法性质。我们知道,从估值日到未来某个现金流日期T_cashflow的折现因子DF(T_evaluation, T_cashflow)可以表示为从估值日到结算日T_settlement的折现因子DF(T_evaluation, T_settlement)与从结算日到现金流日期T_cashflow的折现因子DF(T_settlement, T_cashflow)的乘积:

DF(T_evaluation, T_cashflow) = DF(T_evaluation, T_settlement) * DF(T_settlement, T_cashflow)

因此,我们可以通过简单的除法运算来得到我们所需的、以结算日为参考的折现因子:

使用QuantLib从债券结算日而非估值日提取折现因子

因赛AIGC

因赛AIGC解决营销全链路应用场景

使用QuantLib从债券结算日而非估值日提取折现因子73

查看详情 使用QuantLib从债券结算日而非估值日提取折现因子

DF(T_settlement, T_cashflow) = DF(T_evaluation, T_cashflow) / DF(T_evaluation, T_settlement)

这意味着,我们首先计算从估值日到每个现金流日期的折现因子,以及从估值日到结算日的折现因子,然后将前者除以后者,即可得到以结算日为基准的折现因子。

QuantLib代码实现

以下代码片段演示了如何在QuantLib中实现这一调整,以从债券结算日提取折现因子,并计算相应的现金流价值:

import QuantLib as ql import pandas as pd  # 假设已初始化QuantLib环境,并定义了相关参数 # 例如: # today = ql.Date(1, ql.January, 2023) # ql.Settings.instance().evaluationDate = today # calendar = ql.UnitedStates() # day_count = ql.Actual360() # curve = ql.DiscountCurve(...) # 假设 curve 已经通过 bootstrapping 构建完成 # bond = ql.FixedRateBond(...) # 假设 bond 已经创建,并包含 cashflows  # 模拟 QuantLib 环境和对象 today = ql.Date(15, ql.January, 2024) ql.Settings.instance().evaluationDate = today calendar = ql.UnitedStates() day_count = ql.Actual360()  # 模拟收益率曲线 (示例,实际中应通过bootstrap构建) dates = [today, today + ql.Period(6, ql.Months), today + ql.Period(1, ql.Years), today + ql.Period(2, ql.Years)] rates = [0.03, 0.032, 0.035, 0.04] curve_handle = ql.YieldTermStructureHandle(     ql.ZeroSpreadedTermStructure(         ql.RelinkableHandle(), # 这里通常是原始曲线         ql.Handle(ql.FlatForward(today, 0.0, day_count)), # 简化示例,实际应是 bootstrapped curve         ql.Compounded, ql.Annual, ql.Period(0, ql.Days)     ) ) # 更真实的曲线构建示例 (略) # 例如: # helpers = [ql.DepositRateHelper(...), ql.FraRateHelper(...), ql.FuturesRateHelper(...), ql.SwapRateHelper(...)] # curve = ql.PiecewiseLogLinearDiscountCurve(today, helpers, day_count) # curve_handle = ql.YieldTermStructureHandle(curve)  # 为了示例可运行,我们直接使用一个简化的FlatForward曲线 curve = ql.FlatForward(today, 0.035, day_count, ql.Compounded, ql.Annual) curve_handle = ql.YieldTermStructureHandle(curve)   # 模拟债券及其现金流 issue_date = ql.Date(15, ql.January, 2023) maturity_date = ql.Date(15, ql.January, 2026) settlement_days = 2 face_amount = 100.0 coupon_rate = 0.04 schedule = ql.Schedule(issue_date, maturity_date, ql.Period(ql.Semiannual), calendar,                        ql.Unadjusted, ql.Unadjusted, ql.DateGeneration.Backward, False) bond = ql.FixedRateBond(settlement_days, face_amount, schedule, [coupon_rate], day_count, ql.Following)  # 获取债券结算日 bond_settlement_date = calendar.advance(today, settlement_days, ql.Days) # 确保结算日不早于估值日 if bond_settlement_date < today:     bond_settlement_date = today  # 提取现金流信息并计算折现因子 fields = ['accrualStartDate', 'accrualEndDate', 'date', 'nominal', 'rate',           'amount', 'accrualDays', 'accrualPeriod'] BondCashflows = []  # 计算从估值日到结算日的折现因子,用于后续调整 df_eval_to_settlement = curve_handle.discount(bond_settlement_date)  for cf in list(map(ql.as_fixed_rate_coupon, bond.cashflows())):     # 过滤掉已经支付的现金流,或者只处理未来现金流     if cf.date() < today:         continue # 跳过过去的现金流      row = {fld: getattr(cf, fld)() for fld in fields if hasattr(cf, fld)} # 使用getattr更健壮     row['AccrualPeriod'] = round((row['accrualEndDate'] - row['accrualStartDate']) / 365, 4)      # 1. 计算基于估值日的折现因子 (用于NPV)     row['ZeroRate (NPV)'] = round(curve_handle.zeroRate(row['date'], day_count, ql.Compounded, ql.Annual).rate(), 9)     row['DiscFactor (NPV)'] = round(curve_handle.discount(row['date']), 9)     row['NPV'] = round(row['DiscFactor (NPV)'] * row['amount'], 9)      # 2. 计算基于结算日的折现因子 (用于Dirty Price)     # 首先获取从估值日到当前现金流日期的折现因子     df_eval_to_cashflow = curve_handle.discount(row['date'])     # 然后进行调整     row['DiscFactor (Dirty Price)'] = round(df_eval_to_cashflow / df_eval_to_settlement, 9)      # 这里的ZeroRate (Dirty Price) 实际上是 Forward Rate     # 从结算日到现金流日期的远期零利率     row['ZeroRate (Dirty Price)'] = round(         curve_handle.forwardRate(bond_settlement_date, row['date'], day_count, ql.Compounded, ql.Annual).rate(), 9     )     row['Dirty Price'] = round(row['DiscFactor (Dirty Price)'] * row['amount'], 9)      BondCashflows.append(row)  BondCashflows_df = pd.DataFrame(BondCashflows) print(BondCashflows_df)

代码解释:

  1. df_eval_to_settlement = curve_handle.discount(bond_settlement_date): 这一步计算了从Evaluation Date到Bond Settlement Date的折现因子。这是我们进行调整的除数。
  2. df_eval_to_cashflow = curve_handle.discount(row[‘date’]): 这一步计算了从Evaluation Date到当前现金流日期row[‘date’]的折现因子。
  3. row[‘DiscFactor (Dirty Price)’] = round(df_eval_to_cashflow / df_eval_to_settlement, 9): 这一行是核心的调整逻辑。它通过将从估值日到现金流日期的折现因子除以从估值日到结算日的折现因子,从而得到以结算日为参考的折现因子。
  4. row[‘ZeroRate (Dirty Price)’]: 这里的零利率实际上是从bond_settlement_date到row[‘date’]的远期零利率,可以使用curve_handle.forwardRate()方法直接获取,这与我们调整后的折现因子是相互一致的。

注意事项与总结

  • 日期一致性:确保在计算过程中使用的Evaluation Date、Settlement Date和现金流日期都基于相同的日历和日期计数约定。
  • 曲线有效性:确保您使用的YieldTermStructure对象是准确且最新的,并且已经通过适当的方法(如bootstrap)构建。
  • 远期折现因子与调整:理解curve.discount(start_date, end_date)和curve.discount(date)的区别。前者计算的是远期折现因子,而后者是基于估值日的折现因子。本文提供的调整方法是基于估值日折现因子的比率,来模拟以结算日为基准的折现。
  • 应用场景:这种方法特别适用于需要计算债券的含息价格(Dirty Price)或在结算日之后进行其他估值分析时,因为这些场景通常要求所有现金流都折现到结算日。

通过上述方法,我们可以在QuantLib中灵活地处理不同参考日期的折现因子,从而满足各种复杂的金融计算需求,特别是债券含息价格的精确估算。

text=ZqhQzanResources