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*) |