Daily Pricing of a Bond with QuantLib using Python -
i use quantlib within python price interest rate instruments (derivatives down track) within portfolio context. main requirement pass daily yield curves system price on successive days (let's ignore system performance issues now). question is, have structured example below correctly this? understanding need @ least 1 curve object per day necessary linking etc. have made use of pandas attempt this. guidance on appreciated.
import quantlib ql import math import pandas pd import datetime dt # market parametres calendar = ql.southafrica() bussiness_convention = ql.unadjusted day_count = ql.actual365fixed() interpolation = ql.linear() compounding = ql.compounded compoundingfrequency = ql.quarterly def perdelta(start, end, delta): date_list=[] curr = start while curr < end: date_list.append(curr) curr += delta return date_list def to_datetime(d): return dt.datetime(d.year(),d.month(), d.dayofmonth()) def format_rate(r): return '{0:.4f}'.format(r.rate()*100.00) #quantlib must have dates in date objects dicperiod={'day':ql.days,'week':ql.weeks,'month':ql.months,'year':ql.years} issuedate = ql.date(19,8,2014) maturitydate = ql.date(19,8,2016) #bond schedule schedule = ql.schedule (issuedate, maturitydate, ql.period(ql.quarterly),ql.target(),ql.following, ql.following, ql.dategeneration.forward,false) fixing_days = 0 face_amount = 100.0 def price_floater(myqlvaldate,jindex,jibartermstructure,discount_curve): bond = ql.floatingratebond(settlementdays = 0, faceamount = 100, schedule = schedule, index = jindex, paymentdaycounter = ql.actual365fixed(), spreads=[0.02]) bondengine = ql.discountingbondengine(ql.yieldtermstructurehandle(discount_curve)) bond.setpricingengine(bondengine) ql.settings.instance().evaluationdate = myqlvaldate return [bond.npv() ,bond.cleanprice()] start_date=dt.datetime(2014,8,19) end_date=dt.datetime(2015,8,19) all_dates=perdelta(start_date,end_date,dt.timedelta(days=1)) dtes=[];fixings=[] d in all_dates: if calendar.isbusinessday(ql.quantlib.date(d.day,d.month,d.year)): dtes.append(ql.quantlib.date(d.day,d.month,d.year)) fixings.append(0.1) df_ad=pd.dataframe(all_dates,columns=['valdate']) df_ad['qlvaldate']=df_ad.valdate.map(lambda x:ql.dateparser.parseiso(x.strftime('%y-%m-%d'))) df_ad['jibartermstructure'] = df_ad.qlvaldate.map(lambda x:ql.relinkableyieldtermstructurehandle()) df_ad['discountstructure'] = df_ad.qlvaldate.map(lambda x:ql.relinkableyieldtermstructurehandle()) df_ad['jindex'] = df_ad.jibartermstructure.map(lambda x: ql.jibar(ql.period(3,ql.months),x)) df_ad.jindex.map(lambda x:x.addfixings(dtes, fixings)) df_ad['flatcurve'] = df_ad.apply(lambda r: ql.flatforward(r['qlvaldate'],0.1,ql.actual365fixed(),compounding,compoundingfrequency),axis=1) df_ad.apply(lambda x:x['jibartermstructure'].linkto(x['flatcurve']),axis=1) df_ad.apply(lambda x:x['discountstructure'].linkto(x['flatcurve']),axis=1) df_ad['discount_curve']= df_ad.apply(lambda x:ql.zerospreadedtermstructure(x['discountstructure'],ql.quotehandle(ql.simplequote(math.log(1+0.02)))),axis=1) df_ad['all_in_price']=df_ad.apply(lambda r:price_floater(r['qlvaldate'],r['jindex'],r['jibartermstructure'],r['discount_curve'])[0],axis=1) df_ad['clean_price']=df_ad.apply(lambda r:price_floater(r['qlvaldate'],r['jindex'],r['jibartermstructure'],r['discount_curve'])[1],axis=1) df_plt=df_ad[['valdate','all_in_price','clean_price']] df_plt=df_plt.set_index('valdate') matplotlib import ticker def func(x, pos): s = str(x) ind = s.index('.') return s[:ind] + '.' + s[ind+1:] ax=df_plt.plot() ax.yaxis.set_major_formatter(ticker.funcformatter(func))
thanks luigi ballabio have reworked example above incorporate design principles within quantlib avoid unnecessary calling. static data static , market data varies (i hope). understand better how live objects listen changes in linked variables.
static data following:
- bondengine
- bond
- structurehandles
- historical jibar index
market data varying component
- daily swap curve
- market spread on swap curve
the reworked example below:
import quantlib ql import math import pandas pd import datetime dt import numpy np # market parametres calendar = ql.southafrica() bussiness_convention = ql.unadjusted day_count = ql.actual365fixed() interpolation = ql.linear() compounding = ql.compounded compoundingfrequency = ql.quarterly def perdelta(start, end, delta): date_list=[] curr = start while curr < end: date_list.append(curr) curr += delta return date_list def to_datetime(d): return dt.datetime(d.year(),d.month(), d.dayofmonth()) def format_rate(r): return '{0:.4f}'.format(r.rate()*100.00) #quantlib must have dates in date objects dicperiod={'day':ql.days,'week':ql.weeks,'month':ql.months,'year':ql.years} issuedate = ql.date(19,8,2014) maturitydate = ql.date(19,8,2016) #bond schedule schedule = ql.schedule (issuedate, maturitydate, ql.period(ql.quarterly),ql.target(),ql.following, ql.following, ql.dategeneration.forward,false) fixing_days = 0 face_amount = 100.0 start_date=dt.datetime(2014,8,19) end_date=dt.datetime(2015,8,19) all_dates=perdelta(start_date,end_date,dt.timedelta(days=1)) dtes=[];fixings=[] d in all_dates: if calendar.isbusinessday(ql.quantlib.date(d.day,d.month,d.year)): dtes.append(ql.quantlib.date(d.day,d.month,d.year)) fixings.append(0.1) jibartermstructure = ql.relinkableyieldtermstructurehandle() jindex = ql.jibar(ql.period(3,ql.months), jibartermstructure) jindex.addfixings(dtes, fixings) discountstructure = ql.relinkableyieldtermstructurehandle() bond = ql.floatingratebond(settlementdays = 0, faceamount = 100, schedule = schedule, index = jindex, paymentdaycounter = ql.actual365fixed(), spreads=[0.02]) bondengine = ql.discountingbondengine(discountstructure) bond.setpricingengine(bondengine) spread = ql.simplequote(0.0) discount_curve = ql.zerospreadedtermstructure(jibartermstructure,ql.quotehandle(spread)) discountstructure.linkto(discount_curve) # ...here pricing function... # pricing: def price_floater(myqlvaldate,jibar_curve,credit_spread): credit_spread = math.log(1.0+credit_spread) ql.settings.instance().evaluationdate = myqlvaldate jibartermstructure.linkto(jibar_curve) spread.setvalue(credit_spread) ql.settings.instance().evaluationdate = myqlvaldate return pd.series({'npv': bond.npv(), 'cleanprice': bond.cleanprice()}) # ...and here remaining varying parts: df_ad=pd.dataframe(all_dates,columns=['valdate']) df_ad['qlvaldate']=df_ad.valdate.map(lambda x:ql.dateparser.parseiso(x.strftime('%y-%m-%d'))) df_ad['jibar_curve'] = df_ad.apply(lambda r: ql.flatforward(r['qlvaldate'],0.1,ql.actual365fixed(),compounding,compoundingfrequency),axis=1) df_ad['spread']=np.random.uniform(0.015, 0.025, size=len(df_ad)) # market spread df_ad['all_in_price'], df_ad["clean_price"]=zip(*df_ad.apply(lambda r:price_floater(r['qlvaldate'],r['jibar_curve'],r['spread']),axis=1).to_records())[1:] # plot result df_plt=df_ad[['valdate','all_in_price','clean_price']] df_plt=df_plt.set_index('valdate') matplotlib import ticker def func(x, pos): # formatter function takes tick label , tick position s = str(x) ind = s.index('.') return s[:ind] + '.' + s[ind+1:] # change dot comma ax=df_plt.plot() ax.yaxis.set_major_formatter(ticker.funcformatter(func))
your solution work, creating bond per day kind of goes against grain of library. can create bond , jibar index once, , change evaluation date , corresponding curves; bond detect changes , recalculate.
in general case, like:
# here parts stay same... jibartermstructure = ql.relinkableyieldtermstructurehandle() jindex = ql.jibar(ql.period(3,ql.months), jibartermstructure) jindex.addfixings(dtes, fixings) discountstructure = ql.relinkableyieldtermstructurehandle() bond = ql.floatingratebond(settlementdays = 0, faceamount = 100, schedule = schedule, index = jindex, paymentdaycounter = ql.actual365fixed(), spreads=[0.02]) bondengine = ql.discountingbondengine(discountstructure) bond.setpricingengine(bondengine) # ...here pricing function... def price_floater(myqlvaldate,jibar_curve,discount_curve): ql.settings.instance().evaluationdate = myqlvaldate jibartermstructure.linkto(jibar_curve) discountstructure.linkto(discount_curve) return [bond.npv() ,bond.cleanprice()] # ...and here remaining varying parts: df_ad=pd.dataframe(all_dates,columns=['valdate']) df_ad['qlvaldate']=df_ad.valdate.map(lambda x:ql.dateparser.parseiso(x.strftime('%y-%m-%d'))) df_ad['flatcurve'] = df_ad.apply(lambda r: ql.flatforward(r['qlvaldate'],0.1,ql.actual365fixed(),compounding,compoundingfrequency),axis=1) df_ad['discount_curve']= df_ad.apply(lambda x:ql.zerospreadedtermstructure(jibartermstructure,ql.quotehandle(ql.simplequote(math.log(1+0.02)))),axis=1) df_ad['all_in_price']=df_ad.apply(lambda r:price_floater(r['qlvaldate'],r['flatcurve'],r['discount_curve'])[0],axis=1) df_ad['clean_price']=df_ad.apply(lambda r:price_floater(r['qlvaldate'],r['flatcurve'],r['discount_curve'])[0],axis=1) df_plt=df_ad[['valdate','all_in_price','clean_price']] df_plt=df_plt.set_index('valdate')
now, in general case, above can optimized: you're calling price_floater
twice per date, you're doing twice work. i'm not familiar pandas, i'd guess can make single call , set df_ad['all_in_price']
, df_ad['clean_price']
single assignment.
moreover, there might ways simplify code further depending on use cases. discount curve might instantiated once , spread changed during pricing:
# in "only once" part: spread = ql.simplequote() discount_curve = ql.zerospreadedtermstructure(jibartermstructure,ql.quotehandle(spread)) discountstructure.linkto(discount_curve) # pricing: def price_floater(myqlvaldate,jibar_curve,credit_spread): ql.settings.instance().evaluationdate = myqlvaldate jibartermstructure.linkto(jibar_curve) spread.setvalue(credit_spread) return [bond.npv() ,bond.cleanprice()]
and in varying part, you'll have array of credit spreads intead of array of discount curves.
if curves flat, can same taking advantage of feature: if initialize curve number of days , calendar instead of date, reference date move evaluation date (if number of days 0, evaluation date; if it's 1, next business day, , on).
# once: risk_free = ql.simplequote() jibar_curve = ql.flatforward(0,calendar,ql.quotehandle(risk_free),ql.actual365fixed(),compounding,compoundingfrequency) jibartermstructure.linkto(jibar_curve) # pricing: def price_floater(myqlvaldate,risk_free_rate,credit_spread): ql.settings.instance().evaluationdate = myqlvaldate risk_free.linkto(risk_free_rate) spread.setvalue(credit_spread) return [bond.npv() ,bond.cleanprice()]
and in varying part, you'll replace array of jibar curves simple array of rates.
the above should give same result code, instantiate lot less objects , save memory , increase performance.
one final warning: neither code nor yours work if pandas' map
evaluates results in parallel; you'd end trying set global evaluation date several values simultaneously, , wouldn't go well.
Comments
Post a Comment