|
From: Stats S. <sta...@gm...> - 2025-06-10 04:14:19
|
Hi - I have been trying to use the spread from ql.CashFlows.zSpread to
discount cash flows. I am able to use it in ZeroSpreadedTermStructure and
get the right numbers.
But I would like to understand how to replicate the same output by hand.
The NPV I get is very close, but there is still a small difference which I
suspect will likely get bigger over longer periods.
Does anyone know what is causing this discrepancy? Thanks in advance.
zspread: 0.009989751634951987 ( from ql.CashFlows.zSpread )
zSpread / ZeroSpreadedTermStructure npv: 198.6780*623718125 (CORRECT)*
zSpread manual npv: 198.6780*7379166345*
###########################################
import QuantLib as ql
daycount = ql.Thirty360(ql.Thirty360.USA)
start = ql.Date(1, 1, 2025)
ql.Settings.instance().evaluationDate = start
rates = ( ['SOFR1D', 4.3564348072], ['SOFR1W', 4.3493027605], ['SOFR1M',
4.3238954677], ['SOFR3M', 4.3084328938], ['SOFR6M', 4.2045895664] )
sofr_index = ql.Sofr()
ois_helpers = []
for period, rate in rates:
tenor = period.replace('SOFR','')
ois_helpers.append( ql.OISRateHelper(0,
ql.Period ( tenor ),
ql.QuoteHandle(ql.SimpleQuote(rate/100)),
sofr_index) )
curve = ql.PiecewiseLogCubicDiscount( start, ois_helpers, ql.Actual360() )
handle = ql.YieldTermStructureHandle( curve )
leg = ql.Leg( [ ql.SimpleCashFlow( 100, ql.Date(1,2,2025) ),
ql.SimpleCashFlow( 100, ql.Date(1,3,2025) ) ] )
spreaded_npv = 198.6780623718125
zspread = ql.CashFlows.zSpread(leg, spreaded_npv, curve, daycount,
ql.Compounded, ql.Monthly, True)
print(f"zspread: {zspread}") # zspread: 0.009989751634951987
zspread_quote = ql.SimpleQuote( zspread )
zspreaded_curve = ql.ZeroSpreadedTermStructure( handle,
ql.QuoteHandle(zspread_quote), ql.Compounded, ql.Monthly, daycount )
zspreaded_handle = ql.YieldTermStructureHandle( zspreaded_curve )
zspreaded_npv = ql.CashFlows.npv(leg, zspreaded_handle, True)
print(f"zSpread ZeroSpreadedTermStructure npv: {zspreaded_npv}")
# zSpread
npv: 198.6780623718125 (MATCH)
zero_rates = [ handle.zeroRate( l.date(), daycount, ql.Compounded,
ql.Monthly ).rate() for ii,l in enumerate(leg) ]
# zero_rates = [ handle.zeroRate( (ii+1)/12, ql.Compounded, ql.Monthly
).rate() for ii,l in enumerate(leg) ] # 198.67814955576728 (BIGGER DELTA)
zpread_manual_npv = 100 / (1 + (zero_rates[0] + zspread) / 12) + 100 / ( 1
+ (zero_rates[1] + zspread) / 12 ) ** 2
print(f"zSpread manual npv: {zpread_manual_npv}\n") # manual zSpread npv:
198.67807379166345 (*NO MATCH*)
|