|
From: Arkadiy N. <ark...@gm...> - 2024-05-04 14:06:25
|
Hi Roshan, DId you mean to say that you expected a bond with a coupon set to the par rate from the discount curve to get back to 100 (it appears that's what you are looking at here). In that case, you need to make sure to set your bond's coupon precisely: instead of coupon =[round(yc_linearzero.zeroRate(adate,ql.Actual365Fixed(),ql. Continuous).rate(),6)] create an index with annual payment term (using https://www.quantlib.org/reference/class_quant_lib_1_1_swap_index.html ) and then use .Fixing() method to get an "on-the-curve" coupon for your bond. On Fri, May 3, 2024 at 3:47 AM Roshan Yadav <er....@gm...> wrote: > I am bootstrapping a yield curve using QuantLib. I have implemented a > function to calculate the present value of cash flows for various bonds > using the bootstrapped yield curve. However, I am encountering a difference > in the NPV calculation, particularly at the maturity of the bond. Ideally, > the NPV at the maturity of the bond should be equal to the face value since > it is at par, which in my case is 100. However, the calculated NPV is > slightly different or Is there anything i am missing out. Here's a snippet > of the code and the output: > CODE: > ``` > >> def qlDatetostr(aDate): >> if aDate.month() < 10: >> return str(aDate.dayOfMonth())+'/0'+ >> str(aDate.month())+'/'+str(aDate.year()) >> else: >> return str(aDate.dayOfMonth())+'/'+ >> str(aDate.month())+'/'+str(aDate.year()) >> > valuationdate = ql.Date(2, 5, 2011) >> ql.Settings.instance().evaluationDate = valuationdate >> settlement_days = 0 >> business_convention = ql.Unadjusted >> end_of_month = False >> coupon_freq = ql.Annual >> facevalue = 100 >> depo_maturities = [ql.Period(1, ql.Days),ql.Period(1, >> ql.Months),ql.Period(2, ql.Months),ql.Period(3, >> ql.Months),ql.Period(6,ql.Months),ql.Period(12,ql.Months)] >> depo_rates = [5.2300,5.2310,5.2250,5.2270,5.2280,5.2200] >> # Bond rates >> bond_maturities = >> [ql.Period(2,ql.Years),ql.Period(3,ql.Years),ql.Period(4,ql.Years),ql.Period(5,ql.Years)] >> bond_rates = [5.23,5.24,5.2500,5.2600] >> maturities = depo_maturities+bond_maturities >> calendar = ql.NullCalendar() >> day_count = ql.Actual365Fixed() >> rates = depo_rates+bond_rates >> df_ytm = pd.DataFrame(list(zip(maturities, rates)), >> columns=["Maturities","Curve"], >> index=['']*len(rates)) > > > # Input YTM data >> quotes = [ >> (1, ql.Period(1, ql.Days), 5.2300), >> (1, ql.Period(1, ql.Months), 5.2310), >> (1, ql.Period(2, ql.Months), 5.2250), >> (1, ql.Period(3, ql.Months), 5.2270), >> (1, ql.Period(6, ql.Months), 5.2280), >> (1, ql.Period(1, ql.Years), 5.2200), >> (1, ql.Period(2, ql.Years), 5.2983), >> (1, ql.Period(3, ql.Years), 5.3086), >> (1, ql.Period(4, ql.Years), 5.2500), >> (1, ql.Period(5, ql.Years), 5.2600), >> ] > > > depo_helpers = >> [ql.DepositRateHelper(ql.QuoteHandle(ql.SimpleQuote(r/100)),m,settlement_days,calendar,business_convention,end_of_month,day_count) >> for r, m in zip(depo_rates, depo_maturities)] > > > bond_helpers = [] >> for r, m in zip(bond_rates, bond_maturities): >> termination_date = valuationdate + m >> schedule = ql.Schedule(valuationdate, >> termination_date, >> ql.Period(coupon_freq), >> calendar, >> business_convention, >> business_convention, >> ql.DateGeneration.Backward, >> end_of_month) >> bond_helper = >> ql.FixedRateBondHelper(ql.QuoteHandle(ql.SimpleQuote(facevalue)),settlement_days,facevalue,schedule,[r/100],day_count,business_convention,) >> bond_helpers.append(bond_helper) > > > rate_helpers = depo_helpers+bond_helpers >> yc_linearzero = >> ql.PiecewiseLinearZero(valuationdate,rate_helpers,day_count) >> scheduledates = [calendar.advance(valuationdate,i[1]) for i in quotes] >> > > >> def >> get_val_working(scheduledates,yc_linearzero,facevalue=100,daycount=ql.Actual365Fixed()): >> ref_date = yc_linearzero.referenceDate() >> >> leg_df=pd.DataFrame(columns=['bond_name','start_date','end_date','days','yearfrac','rate','cashflow','discountFactor','calc_df','zerorate',"npv","cf_npv","cf_cum_npv","cum_npv",'eq_rate','calc_yield']) > > calendar = ql.NullCalendar() >> settlement_days = 0 >> facevalue = 100 >> ctr = 0 >> curve_handle = ql.RelinkableYieldTermStructureHandle() >> bondEngine = ql.DiscountingBondEngine(curve_handle) >> for adate in scheduledates: >> schedule = ql.Schedule(ref_date,adate, ql.Period(ql.Semiannual), >> calendar,ql.Unadjusted, ql.Unadjusted, ql.DateGeneration.Forward,False) >> coupon >> =[round(yc_linearzero.zeroRate(adate,ql.Actual365Fixed(),ql.Continuous).rate(),6)] >> bond = >> ql.FixedRateBond(settlement_days,facevalue,schedule,coupon,daycount) >> bond_name = f"bond {schedule.endDate()} {coupon}%" >> # flat_forward = >> ql.FlatForward(0,ql.NullCalendar(),coupon[0],ql.Thirty360(),ql.Compounded,ql.Annual) >> curve_handle.linkTo(yc_linearzero) >> bond.setPricingEngine(bondEngine) >> cum_npv = 0 >> cf_cum_npv = 0 >> for index, cf in enumerate(bond.cashflows()): >> date = schedule.previousDate(cf.date()) if >> schedule.startDate() < cf.date() else cf.date() >> nxtdate = cf.date() >> # ql.Settings.instance().evaluationDate = date >> yearfrac = day_count.yearFraction(ref_date,cf.date()) >> cf_amount = cf.amount() >> days = day_count.dayCount(ref_date,cf.date()) >> zerorate = yc_linearzero.zeroRate(yearfrac,ql.Continuous) >> eq_rate = >> zerorate.equivalentRate(day_count,ql.Compounded,ql.Semiannual,ref_date,cf.date()) >> discountFactor = zerorate.discountFactor(ref_date,cf.date()) >> # df_calc = 1/((1+zerorate.rate())**yearfrac) >> df_calc = >> round(np.exp(-day_count.yearFraction(ref_date,cf.date()) * >> zerorate.rate()), 8) >> calc_yield = 1/(discountFactor**(1/yearfrac))-1 >> npv = df_calc*cf.amount() >> cf_npv = ql.CashFlows_npv([cf],curve_handle,True) >> cum_npv = cum_npv+npv >> cf_cum_npv = cf_cum_npv+cf_npv >> >> row={"bond_name":bond_name,'start_date':qlDatetostr(date),'end_date':qlDatetostr(nxtdate),'days':days,'yearfrac':yearfrac,'rate':coupon, >> 'cashflow':cf_amount,'discountFactor':discountFactor,'calc_df':df_calc,'calc_yield':calc_yield,'zerorate':zerorate.rate(),'npv':npv,"cf_npv":cf_npv,"cf_cum_npv":cf_cum_npv,"cum_npv":cum_npv,'eq_rate':eq_rate.rate()} >> leg_df = leg_df.append(row,ignore_index=True) >> if ctr == len(df_ytm): >> break >> else: >> ctr = ctr+1 >> return leg_df >> valuation = get_val_working(scheduledates,yc_linearzero) > > print(valuation.tail(11)) > > > which gives: > >OUTPUT: > > bond_name start_date end_date days yearfrac rate cashflow discountFactor > calc_df zerorate npv cf_npv cf_cum_npv cum_npv eq_rate calc_yield > 34 bond May 2nd, 2016 [0.051283]% 02-05-2011 02-11-2011 184 0.504109589 > [0.051283] 2.585225205 0.974321893 0.97432189 0.051602954 2.518841508 > 2.518841517 2.518841517 2.518841508 0.052274433 0.052957587 > 35 bond May 2nd, 2016 [0.051283]% 02-11-2011 02-05-2012 366 1.002739726 > [0.051283] 2.557124932 0.950260502 0.9502605 0.050879723 2.429934816 > 2.42993482 4.948776337 4.948776324 0.051532433 0.052196331 > 36 bond May 2nd, 2016 [0.051283]% 02-05-2012 02-11-2012 550 1.506849315 > [0.051283] 2.585225205 0.926127394 0.92612739 0.050929763 2.394247872 > 2.394247883 7.34302422 7.343024196 0.051583763 0.052248984 > 37 bond May 2nd, 2016 [0.051283]% 02-11-2012 02-05-2013 731 2.002739726 > [0.051283] 2.543074795 0.902941381 0.90294138 0.050978988 2.296247464 > 2.296247467 9.639271687 9.639271661 0.051634258 0.052300782 > 38 bond May 2nd, 2016 [0.051283]% 02-05-2013 02-11-2013 915 2.506849315 > [0.051283] 2.585225205 0.879921283 0.87992128 0.051029324 2.274794672 > 2.274794679 11.91406637 11.91406633 0.051685895 0.052353753 > 39 bond May 2nd, 2016 [0.051283]% 02-11-2013 02-05-2014 1096 3.002739726 > [0.051283] 2.543074795 0.857806728 0.85780673 0.051078841 2.181466674 > 2.181466668 14.09553303 14.09553301 0.051736691 0.052405862 > 40 bond May 2nd, 2016 [0.051283]% 02-05-2014 02-11-2014 1280 3.506849315 > [0.051283] 2.585225205 0.835850894 0.83585089 0.051129952 2.160862789 > 2.160862798 16.25639583 16.2563958 0.051789126 0.052459654 > 41 bond May 2nd, 2016 [0.051283]% 02-11-2014 02-05-2015 1461 4.002739726 > [0.051283] 2.543074795 0.814760443 0.81476044 0.051180231 2.071996739 > 2.071996747 18.32839258 18.32839253 0.051840707 0.052512571 > 42 bond May 2nd, 2016 [0.051283]% 02-05-2015 02-11-2015 1645 4.506849315 > [0.051283] 2.585225205 0.793823805 0.79382381 0.051231744 2.052213322 > 2.05221331 20.38060589 20.38060586 0.051893556 0.052566791 > 43 bond May 2nd, 2016 [0.051283]% 02-11-2015 02-05-2016 1827 5.005479452 > [0.051283] 2.557124932 0.773604524 0.77360452 0.051282697 1.978203405 > 1.978203416 22.35880931 22.35880926 0.051945832 0.052620424 > 44 bond May 2nd, 2016 [0.051283]% 02-11-2015 02-05-2016 1827 5.005479452 > [0.051283] *100* 0.773604524 0.77360452 0.051282697 77.360452 77.36045243 > 99.71926174 99.71926126 0.051945832 0.052620424 > > > > _______________________________________________ > QuantLib-users mailing list > Qua...@li... > https://lists.sourceforge.net/lists/listinfo/quantlib-users > |