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

Popular posts from this blog

html - Outlook 2010 Anchor (url/address/link) -

javascript - Why does running this loop 9 times take 100x longer than running it 8 times? -

Getting gateway time-out Rails app with Nginx + Puma running on Digital Ocean -