Valuing Callable Bonds in QuantLib Python

Goutham Balaraman

In this post, I will walk you through on how to value callable bonds in QuantLib Python. The approach to construct a callable bond is lot similar to creating a fixed rate bond in QuantLib. The one additional input that we need to provide here is the details on the callable schedule. If you follow the fixed rate bond example already, this should be fairly straight forward.

As always, we will start with some initializations and imports.


In [1]:
import QuantLib as ql
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
calc_date = ql.Date(16,8,2016)
ql.Settings.instance().evaluationDate = calc_date

For simplicity, I am going to assume that the interest rate term structure is a flat yield curve at 3.5%.


In [2]:
day_count = ql.ActualActual(ql.ActualActual.Bond)
rate = 0.035
ts = ql.FlatForward(calc_date, 
                    rate, 
                    day_count, 
                    ql.Compounded, 
                    ql.Semiannual)
ts_handle = ql.YieldTermStructureHandle(ts)

The call and put schedules for the callable bond is created as shown below. I create a container for holding all the call and put dates using the CallabilitySchedule class. You can add each call using Callability class and noting as Callability.Call or Callability.Put for either a call or put.


In [3]:
callability_schedule = ql.CallabilitySchedule()
call_price = 100.0
call_date = ql.Date(15,ql.September,2016);
null_calendar = ql.NullCalendar();
for i in range(0,24):
    callability_price  = ql.CallabilityPrice(
        call_price, ql.CallabilityPrice.Clean)
    callability_schedule.append(
            ql.Callability(callability_price, 
                           ql.Callability.Call,
                           call_date))

    call_date = null_calendar.advance(call_date, 3, ql.Months);

What follows next is very similar to the Schedule that we created in the vanilla fixed rate bond valuation.


In [4]:
issue_date = ql.Date(16,ql.September,2014)        
maturity_date = ql.Date(15,ql.September,2022)
calendar = ql.UnitedStates(ql.UnitedStates.GovernmentBond)
tenor = ql.Period(ql.Quarterly)
accrual_convention = ql.Unadjusted

schedule = ql.Schedule(issue_date, maturity_date, tenor,
                       calendar, accrual_convention, accrual_convention,
                       ql.DateGeneration.Backward, False)

The callable bond is instantiated using the CallableFixedRateBond class, which accepts the bond inputs and the call / put schedule.


In [5]:
settlement_days = 3
face_amount = 100
accrual_daycount = ql.ActualActual(ql.ActualActual.Bond)
coupon = 0.025


bond = ql.CallableFixedRateBond(
    settlement_days, face_amount,
    schedule, [coupon], accrual_daycount,
    ql.Following, face_amount, issue_date,
    callability_schedule)

In order to value the bond, we need an interest rate model to model the fact that the bond will get called or not in the future depending on where the future interest rates are at. The TreeCallableFixedRateBondEngine can be used to value the callable bond. Below, the value_bond function prices the callable bond based on the Hull-White model parameter for mean reversion and volatility.


In [6]:
def value_bond(a, s, grid_points, bond):
    model = ql.HullWhite(ts_handle, a, s)
    engine = ql.TreeCallableFixedRateBondEngine(model, grid_points)
    bond.setPricingEngine(engine)
    return bond

The callable bond value for a 3% mean reversion and 12% volatility is shown below.


In [7]:
value_bond(0.03, 0.12, 40, bond)
print "Bond price: ",bond.cleanPrice()


Bond price:  68.3769646975

The price sensitivity of callable bonds to that of volatility parameter is shown below. As volatility increases, there is a higher chance of it being callable. Hence the value of the bond decreases.


In [8]:
sigmas = np.arange(0.001, 0.15, 0.001)
prices = [value_bond(0.03, s, 40, bond).cleanPrice() 
          for s in sigmas]

In [9]:
plt.plot(sigmas, prices)


Out[9]:
[<matplotlib.lines.Line2D at 0x7fa0abbdbd90>]

The static cashflows can be accessed using the cashflows accessor.


In [10]:
for c in bond.cashflows():
    print c.date(), "     ", c.amount()


December 15th, 2,014       0.618131868132
March 16th, 2,015       0.625
June 15th, 2,015       0.625
September 15th, 2,015       0.625
December 15th, 2,015       0.625
March 15th, 2,016       0.625
June 15th, 2,016       0.625
September 15th, 2,016       0.625
December 15th, 2,016       0.625
March 15th, 2,017       0.625
June 15th, 2,017       0.625
September 15th, 2,017       0.625
December 15th, 2,017       0.625
March 15th, 2,018       0.625
June 15th, 2,018       0.625
September 17th, 2,018       0.625
December 17th, 2,018       0.625
March 15th, 2,019       0.625
June 17th, 2,019       0.625
September 16th, 2,019       0.625
December 16th, 2,019       0.625
March 16th, 2,020       0.625
June 15th, 2,020       0.625
September 15th, 2,020       0.625
December 15th, 2,020       0.625
March 15th, 2,021       0.625
June 15th, 2,021       0.625
September 15th, 2,021       0.625
December 15th, 2,021       0.625
March 15th, 2,022       0.625
June 15th, 2,022       0.625
September 15th, 2,022       0.625
September 15th, 2,022       100.0

Conclusion

Here we explored a minimal example on pricing a callable bond.