1. QuantLib basics
In this chapter we will introduce some of the basic concepts such as Date, Period, Calendar and Schedule. These are QuantLib constructs that are used throughout the library in creation of instruments, models, term structures etc.
In [1]: import QuantLib as ql
import pandas as pd
Date Class
The Date object can be created using the constructor as Date(day, month, year). It would be worthwhile to pay attention to the fact that day is the first argument, followed by month and then the year. This is different from the Python datetime object instantiation.
In [2]: date = ql.Date(31, 3, 2015)
print(date)
Out[2]: March 31st, 2015
The fields of the Date object can be accessed using the month(), dayOfMonth() and year() methods. The weekday() method can be used to fetch the day of the week.
In [3]: print("%d-%d-%d" %(date.month(),
date.dayOfMonth(),
date.year()))
Out[3]: 3-31-2015
In [4]: date.weekday() == ql.Tuesday
Out[4]: True
The Date objects can also be used to perform arithmetic operations such as advancing by days, weeks, months etc. Periods such as weeks or months can be denoted using the Period class. Period object constructor signature is Period(num_periods, period_type). The num_periods is an integer and represents the number of periods. The period_type can be Weeks, Months and Years.
In [5]: type(date+1)
Out[5]: QuantLib.QuantLib.Date
In [6]: print("Add a day : {0}".format(date + 1))
print("Subtract a day : {0}".format(date - 1))
print("Add a week : {0}".format(date + ql.Period(1, ql.Weeks)))
print("Add a month : {0}".format(date + ql.Period(1, ql.Months)))
print("Add a year : {0}".format(date + ql.Period(1, ql.Years)))
Out[6]: Add a day : April 1st, 2015
Subtract a day : March 30th, 2015
Add a week : April 7th, 2015
Add a month : April 30th, 2015
Add a year : March 31st, 2016
One can also do logical operations using the Date object.
In [7]: print(date == ql.Date(31, 3, 2015))
print(date > ql.Date(30, 3, 2015))
print(date < ql.Date(1, 4, 2015))
print(date != ql.Date(1, 4, 2015))
Out[7]: True
True
True
True
The Date object is used in setting valuation dates, issuance and expiry dates of instruments. The Period object is used in setting tenors, such as that of coupon payments, or in constructing payment schedules.
Calendar Class
The Date arithmetic above did not take holidays into account. But valuation of different securities would require taking into account the holidays observed in a specific exchange or country. The Calendar class implements this functionality for all the major exchanges. Let us take a look at a few examples here.
In [8]: date = ql.Date(31, 3, 2015)
us_calendar = ql.UnitedStates(ql.UnitedStates.GovernmentBond)
italy_calendar = ql.Italy()
period = ql.Period(60, ql.Days)
raw_date = date + period
us_date = us_calendar.advance(date, period)
italy_date = italy_calendar.advance(date, period)
print("Add 60 days: {0}".format(raw_date))
print("Add 60 business days in US: {0}".format(us_date))
print("Add 60 business days in Italy: {0}".format(italy_date))
Out[8]: Add 60 days: May 30th, 2015
Add 60 business days in US: June 24th, 2015
Add 60 business days in Italy: June 26th, 2015
The addHoliday and removeHoliday methods in the calendar can be used to add and remove holidays to the calendar respectively. If a calendar has any missing holidays or has a wrong holiday, then these methods come handy in fixing the errors. The businessDaysBetween method helps find out the number of business days between two dates per a given calendar. Let us use this method on the us_calendar and italy_calendar as a sanity check.
In [9]: us_busdays = us_calendar.businessDaysBetween(date, us_date)
italy_busdays = italy_calendar.businessDaysBetween(date, italy_date)
print("Business days US: {0}".format(us_busdays))
print("Business days Italy: {0}".format(italy_busdays))
Out[9]: Business days US: 60
Business days Italy: 60
In valuation of certain deals, more than one calendar’s holidays are observed. QuantLib has JointCalendar class that allows you to combine the holidays of two or more calendars. Let us take a look at a working example.
In [10]: joint_calendar = ql.JointCalendar(us_calendar, italy_calendar)
joint_date = joint_calendar.advance(date, period)
joint_busdays = joint_calendar.businessDaysBetween(date, joint_date)
print("Add 60 business days in US-Italy: {0}".format(joint_date))
print("Business days US-Italy: {0}".format(joint_busdays))
Out[10]: Add 60 business days in US-Italy: June 29th, 2015
Business days US-Italy: 60
Schedule Class
The Schedule object is necessary in creating coupon schedules or call schedules. Schedule object constructors have the following signature:
Schedule(const Date& effectiveDate,
const Date& terminationDate,
const Period& tenor,
const Calendar& calendar,
BusinessDayConvention convention,
BusinessDayConvention terminationDateConvention,
DateGeneration::Rule rule,
bool endOfMonth,
const Date& firstDate = Date(),
const Date& nextToLastDate = Date())
and
Schedule(const std::vector<Date>&,
const Calendar& calendar,
BusinessDayConvention rollingConvention)
In [11]: effective_date = ql.Date(1, 1, 2015)
termination_date = ql.Date(1, 1, 2016)
tenor = ql.Period(ql.Monthly)
calendar = ql.UnitedStates(ql.UnitedStates.GovernmentBond)
business_convention = ql.Following
termination_business_convention = ql.Following
date_generation = ql.DateGeneration.Forward
end_of_month = False
schedule = ql.Schedule(effective_date,
termination_date,
tenor,
calendar,
business_convention,
termination_business_convention,
date_generation,
end_of_month)
pd.DataFrame({'date': list(schedule)})
Out[11]:
| date | |
|---|---|
| 0 | January 2nd, 2015 |
| 1 | February 2nd, 2015 |
| 2 | March 2nd, 2015 |
| 3 | April 1st, 2015 |
| 4 | May 1st, 2015 |
| 5 | June 1st, 2015 |
| 6 | July 1st, 2015 |
| 7 | August 3rd, 2015 |
| 8 | September 1st, 2015 |
| 9 | October 1st, 2015 |
| 10 | November 2nd, 2015 |
| 11 | December 1st, 2015 |
| 12 | January 4th, 2016 |
Here we have generated a Schedule object that will contain dates between effective_date and termination_date with the tenor specifying the Period to be Monthly. The calendar object is used for determining holidays. Here we have chosen the convention to be the day following holidays. That is why we see that holidays are excluded in the list of dates.
The Schedule class can handle generation of dates with irregularity in schedule. The two extra parameters firstDate and nextToLastDate parameters along with a combination of forward or backward date generation rule can be used to generate short or long stub payments at the front or back end of the schedule. For example, the following combination of firstDate and backward generation rule creates a short stub in the front on the January 15, 2015.
In [12]: # short stub in the front
effective_date = ql.Date(1, 1, 2015)
termination_date = ql.Date(1, 1, 2016)
first_date = ql.Date(15, 1, 2015)
schedule = ql.Schedule(effective_date,
termination_date,
tenor,
calendar,
business_convention,
termination_business_convention,
ql.DateGeneration.Backward,
end_of_month,
first_date)
pd.DataFrame({'date': list(schedule)})
Out[12]:
| date | |
|---|---|
| 0 | January 2nd, 2015 |
| 1 | January 15th, 2015 |
| 2 | February 2nd, 2015 |
| 3 | March 2nd, 2015 |
| 4 | April 1st, 2015 |
| 5 | May 1st, 2015 |
| 6 | June 1st, 2015 |
| 7 | July 1st, 2015 |
| 8 | August 3rd, 2015 |
| 9 | September 1st, 2015 |
| 10 | October 1st, 2015 |
| 11 | November 2nd, 2015 |
| 12 | December 1st, 2015 |
| 13 | January 4th, 2016 |
Using the nextToLastDate parameter along with the forward date generation rule creates a short stub at the back end of the schedule.
In [13]: # short stub at the back
effective_date = ql.Date(1, 1, 2015)
termination_date = ql.Date(1, 1, 2016)
penultimate_date = ql.Date(15, 12, 2015)
schedule = ql.Schedule(effective_date,
termination_date,
tenor,
calendar,
business_convention,
termination_business_convention,
ql.DateGeneration.Forward,
end_of_month,
ql.Date(),
penultimate_date)
pd.DataFrame({'date': list(schedule)})
Out[13]:
| date | |
|---|---|
| 0 | January 2nd, 2015 |
| 1 | February 2nd, 2015 |
| 2 | March 2nd, 2015 |
| 3 | April 1st, 2015 |
| 4 | May 1st, 2015 |
| 5 | June 1st, 2015 |
| 6 | July 1st, 2015 |
| 7 | August 3rd, 2015 |
| 8 | September 1st, 2015 |
| 9 | October 1st, 2015 |
| 10 | November 2nd, 2015 |
| 11 | December 1st, 2015 |
| 12 | December 15th, 2015 |
| 13 | January 4th, 2016 |
Using the backward generation rule along with the firstDate allows us to create a long stub in the front. Below the first two dates are longer in duration than the rest of the dates.
In [14]: # long stub in the front
first_date = ql.Date(1, 2, 2015)
effective_date = ql.Date(15, 12, 2014)
termination_date = ql.Date(1, 1, 2016)
schedule = ql.Schedule(effective_date,
termination_date,
tenor,
calendar,
business_convention,
termination_business_convention,
ql.DateGeneration.Backward,
end_of_month,
first_date)
pd.DataFrame({'date': list(schedule)})
Out[14]:
| date | |
|---|---|
| 0 | December 15th, 2014 |
| 1 | February 2nd, 2015 |
| 2 | March 2nd, 2015 |
| 3 | April 1st, 2015 |
| 4 | May 1st, 2015 |
| 5 | June 1st, 2015 |
| 6 | July 1st, 2015 |
| 7 | August 3rd, 2015 |
| 8 | September 1st, 2015 |
| 9 | October 1st, 2015 |
| 10 | November 2nd, 2015 |
| 11 | December 1st, 2015 |
| 12 | January 4th, 2016 |
Similarly the usage of nextToLastDate parameter along with forward date generation rule can be used to generate long stub at the back of the schedule.
In [15]: # long stub at the back
effective_date = ql.Date(1, 1, 2015)
penultimate_date = ql.Date(1, 12, 2015)
termination_date = ql.Date(15, 1, 2016)
schedule = ql.Schedule(effective_date,
termination_date,
tenor,
calendar,
business_convention,
termination_business_convention,
ql.DateGeneration.Forward,
end_of_month,
ql.Date(),
penultimate_date)
pd.DataFrame({'date': list(schedule)})
Out[15]:
| date | |
|---|---|
| 0 | January 2nd, 2015 |
| 1 | February 2nd, 2015 |
| 2 | March 2nd, 2015 |
| 3 | April 1st, 2015 |
| 4 | May 1st, 2015 |
| 5 | June 1st, 2015 |
| 6 | July 1st, 2015 |
| 7 | August 3rd, 2015 |
| 8 | September 1st, 2015 |
| 9 | October 1st, 2015 |
| 10 | November 2nd, 2015 |
| 11 | December 1st, 2015 |
| 12 | January 15th, 2016 |
Below the Schedule is generated from a list of dates.
In [16]: dates = [ql.Date(2,1,2015), ql.Date(2, 2,2015),
ql.Date(2,3,2015), ql.Date(1,4,2015),
ql.Date(1,5,2015), ql.Date(1,6,2015),
ql.Date(1,7,2015), ql.Date(3,8,2015),
ql.Date(1,9,2015), ql.Date(1,10,2015),
ql.Date(2,11,2015), ql.Date(1,12,2015),
ql.Date(4,1,2016)]
rolling_convention = ql.Following
schedule = ql.Schedule(dates, calendar,
rolling_convention)
pd.DataFrame({'date': list(schedule)})
Out[16]:
| date | |
|---|---|
| 0 | January 2nd, 2015 |
| 1 | February 2nd, 2015 |
| 2 | March 2nd, 2015 |
| 3 | April 1st, 2015 |
| 4 | May 1st, 2015 |
| 5 | June 1st, 2015 |
| 6 | July 1st, 2015 |
| 7 | August 3rd, 2015 |
| 8 | September 1st, 2015 |
| 9 | October 1st, 2015 |
| 10 | November 2nd, 2015 |
| 11 | December 1st, 2015 |
| 12 | January 4th, 2016 |
Interest Rate
The InterestRate class can be used to store the interest rate with the compounding type, day count and
the frequency of compounding. Below we show how to create an interest rate of 5.0% compounded annually,
using Actual/Actual day count convention.
In [17]: annual_rate = 0.05
day_count = ql.ActualActual(ql.ActualActual.ISDA)
compound_type = ql.Compounded
frequency = ql.Annual
interest_rate = ql.InterestRate(annual_rate,
day_count,
compound_type,
frequency)
print(interest_rate)
Out[17]: 5.000000 % Actual/Actual (ISDA) Annual compounding
Lets say if you invest a dollar at the interest rate described by interest_rate, the
compoundFactor method in the InterestRate object gives you how much your investment will be worth after any period.
Below we show that the value returned by compound_factor for 2 years agrees with
the expected compounding formula.
In [18]: t = 2.0
print(interest_rate.compoundFactor(t))
print((1+annual_rate)*(1.0+annual_rate))
Out[18]: 1.1025
1.1025
The discountFactor method returns the reciprocal of the compoundFactor method.
The discount factor is useful while calculating the present value of future cashflows.
In [19]: print(interest_rate.discountFactor(t))
print(1.0/interest_rate.compoundFactor(t))
Out[19]: 0.9070294784580498
0.9070294784580498
A given interest rate can be converted into other compounding types and compounding frequency using the equivalentRate method.
In [20]: new_frequency = ql.Semiannual
new_interest_rate = interest_rate.equivalentRate(compound_type,
new_frequency, t)
print(new_interest_rate)
Out[20]: 4.939015 % Actual/Actual (ISDA) Semiannual compounding
The discount factor for the two InterestRate objects, interest_rate and new_interest_rate are the same, as shown below.
In [21]: print(interest_rate.discountFactor(t))
print(new_interest_rate.discountFactor(t))
Out[21]: 0.9070294784580498
0.9070294784580495
The impliedRate method in the InterestRate class
takes compound factor to return the implied rate. The impliedRate method
is a static method in the InterestRate class and can be used without an
instance of InterestRate. Internally the equivalentRate method invokes
the impliedRate method in its calculations.
Conclusion
This chapter gave an introduction to the basics of QuantLib. Here we explained the Date, Schedule, Calendar and InterestRate classes.