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.