Calculating Duration and Convexity for Irregular Cash Flows in QuantLib
Summary
The document explains how to calculate duration and convexity for irregular cash flows in QuantLib, either by constructing a bond or by calling the CashFlows functions directly. The key syntax is to pass an InterestRate object and an include-settlement-date-flows flag; the example uses a Leg made from SimpleCashFlow objects and sets that flag to false. The same functions can also accept a list of cash flows.
A toy example compares cash-flow present values under two day-count conventions with a custom bond, then checks bond and cash-flow duration and convexity against a manual convexity calculation. The outputs agree in this setup. The discussion also clarifies that a Leg is simply a cash-flow collection, and that empty settlement and NPV dates default to the evaluation date and adjusted settlement date, respectively. The example is narrowly illustrative; correct dates, settlement-flow treatment, and rate conventions still matter in other cases.
Key ideas
- CashFlows duration and convexity methods can operate on irregular cash flows without requiring a bond object.
- Pass an InterestRate object and the include-settlement-date-flows flag to match the expected function signature.
- A Leg and a cash-flow list represent the same collection for these calculations.
- An empty settlement date defaults to the evaluation date, while an empty NPV date defaults to settlement.
Tags
Full text
# 61015
# QuantLib Python: how to calculate duration and convexity for irregular cashflows? Can I use SimpleCashFlow or must I define a custom bond?
I have 2 questions:
If I want to discount a set of irregular cashflows, I can do it using the SimpleCashFlow class, or defining a bond with custom cashflows (thank you to Ballabio and David Duarte for their previous clarifications on this. I have copied a toy example below.
- However, can I calculate duration and convexity with SimpleCashFlow, too, or does that work only for bonds?
I see it in the docs that CashFlows does have the `duration` and `convexity` methods, but I can't seem to get the syntax right.
The last lines of my toy example are what I have tried - to no avail.
- May I ask where this is documented, or how to interpret the documentation? The lines I wrote seem to match the syntax I find on https://quantlib-python-docs.readthedocs.io/en/latest/cashflows.html?highlight=duration#id5 , but evidently I am wrong because they don't work.
If instead I search github https://rkapl123.github.io/QLAnnotatedSource/d3/d50/struct_quant_lib_1_1_duration.html I honestly understand absolutely nothing at all on how the syntax should look like.
Could any one please be kind enough to point me to which docs/where explain what the correct syntax should be? I am basically in a position where I can't make sense of the docs and must ask for help for even the most banal calculations. Thank you!
```
import QuantLib as ql
import pandas as pd
from datetime import date
import numpy as np
d1 = ql.Date(15,1,2001)
ql.Settings.instance().setEvaluationDate(d1)
cfs = [ql.SimpleCashFlow(0, d1),
ql.SimpleCashFlow(110, d1 + 365)]
calc_date = d1
risk_free_rate = 0.1
curve_act_365 = ql.YieldTermStructureHandle(
ql.FlatForward(calc_date, risk_free_rate, ql.Actual365Fixed() , ql.Compounded, ql.Annual ))
curve_30_360 = ql.YieldTermStructureHandle(
ql.FlatForward(calc_date, risk_free_rate, ql.Thirty360(), ql.Compounded, ql.Annual ))
# The present value is the same 9as it should be!)
# whether I calculate it as
# 1) cashflows - act365
# 2) cashflows - 30/360
# 3) custom bond - act465
pv_act_365 = ql.CashFlows.npv(cfs, curve_act_365, True)
pv_30_360 = ql.CashFlows.npv(cfs, curve_30_360, True)
custom_bond = ql.Bond(0, ql.TARGET(), 100.0, ql.Date(), ql.Date(), cfs)
bond_engine = ql.DiscountingBondEngine(curve_act_365)
custom_bond.setPricingEngine(bond_engine)
pv_bond = custom_bond.NPV()
# I can also use the custom bond to calculate duration and convexity
y = ql.InterestRate(0.1, ql.Actual365Fixed(), ql.Compounded, ql.Annual)
dur_bond = ql.BondFunctions.duration(custom_bond, y, ql.Duration.Macaulay)
conv_bond = ql.BondFunctions.convexity(custom_bond, y)
# manually recalculating the convexity just as a check
conv_check = 1 / (100 * 1.1**2)*110* (1+1**2)/(1.1)
# Can I use the CashFlows class to calculate duration and convexity, too?
# I have tried all the combinations below but none works
cf_dur = ql.CashFlows.duration(cfs, y, ql.Duration.Macaulay)
leg = ql.Leg(cfs)
cf_dur = ql.CashFlows.duration(leg, y, ql.Duration.Macaulay)
cf_dur = ql.CashFlows.duration(leg, 0.1, ql.Duration.Macaulay, False)
cf_conv = x = ql.CashFlows.convexity(cfs, y)
```
### UPDATE in light of Francis' answer below:
- So the duration function can take either a list of dates, or a Leg object created from the same list, right? Is there any difference between the two?
- The docs state that `settlementDate = ql.Date()` and that `npvDate = ql.Date()`. Does this mean that ql.Date() is the default value? Or that they are of type ql.Date()? I don't understand, because running ql.Date() returns an empty object. I would have expected `ql.Date()` to equal today's date, or the date set with `ql.Settings.instance().setEvaluationDate(mydate)` .
## Answer by Francis (score 2, accepted)
https://quant.stackexchange.com/a/61021
I think that you almost had both. You were just missing the combination of an `InterestRate` and the `includeSettlementDateFlows` boolean flag. The following works:
```
import QuantLib as Ql
import logging
ql = Ql
# Set up logging
numeric_level = getattr(logging, 'INFO', None)
logging.basicConfig(level=numeric_level, format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
d1 = ql.Date(15, 1, 2001)
ql.Settings.instance().setEvaluationDate(d1)
cfs = [ql.SimpleCashFlow(0, d1), ql.SimpleCashFlow(110, d1 + 365)]
calc_date = d1
risk_free_rate = 0.1
curve_act_365 = ql.YieldTermStructureHandle(
ql.FlatForward(calc_date, risk_free_rate, ql.Actual365Fixed(), ql.Compounded, ql.Annual))
curve_30_360 = ql.YieldTermStructureHandle(
ql.FlatForward(calc_date, risk_free_rate, ql.Thirty360(), ql.Compounded, ql.Annual))
# The present value is the same 9as it should be!)
# whether I calculate it as
# 1) cashflows - act365
# 2) cashflows - 30/360
# 3) custom bond - act465
pv_act_365 = ql.CashFlows.npv(cfs, curve_act_365, True)
logging.info('pv_act_365: %f', pv_act_365)
pv_30_360 = ql.CashFlows.npv(cfs, curve_30_360, True)
logging.info('pv_30_360: %f', pv_30_360)
custom_bond = ql.Bond(0, ql.TARGET(), 100.0, ql.Date(), ql.Date(), cfs)
bond_engine = ql.DiscountingBondEngine(curve_act_365)
custom_bond.setPricingEngine(bond_engine)
pv_bond = custom_bond.NPV()
logging.info('pv_bond: %f', pv_bond)
# I can also use the custom bond to calculate duration and convexity
y = ql.InterestRate(0.1, ql.Actual365Fixed(), ql.Compounded, ql.Annual)
dur_bond = ql.BondFunctions.duration(custom_bond, y, ql.Duration.Macaulay)
conv_bond = ql.BondFunctions.convexity(custom_bond, y)
logging.info('dur_bond: %f', dur_bond)
logging.info('conv_bond: %f', conv_bond)
# manually recalculating the convexity just as a check
conv_check = 1 / (100 * 1.1 ** 2) * 110 * (1 + 1 ** 2) / 1.1
logging.info('conv_check: %f', conv_check)
# Can I use the CashFlows class to calculate duration and convexity, too?
# I have tried all the combinations below but none works
# cf_dur = ql.CashFlows.duration(cfs, y, ql.Duration.Macaulay)
# logging.info('cf_dur: %f', cf_dur)
leg = ql.Leg(cfs)
# cf_dur = ql.CashFlows.duration(leg, y, ql.Duration.Macaulay)
# logging.info('cf_dur: %f', cf_dur)
cf_dur = ql.CashFlows.duration(leg, y, ql.Duration.Macaulay, False)
logging.info('cf_dur: %f', cf_dur)
cf_conv = x = ql.CashFlows.convexity(cfs, y, False)
logging.info('cf_conv: %f', cf_conv)
```
and outputs
```
2021-02-07 20:09:07 INFO pv_act_365: 100.000000
2021-02-07 20:09:07 INFO pv_30_360: 100.000000
2021-02-07 20:09:07 INFO pv_bond: 100.000000
2021-02-07 20:09:07 INFO dur_bond: 1.000000
2021-02-07 20:09:07 INFO conv_bond: 1.652893
2021-02-07 20:09:07 INFO conv_check: 1.652893
2021-02-07 20:09:07 INFO cf_dur: 1.000000
2021-02-07 20:09:07 INFO cf_conv: 1.652893
```
I should have linked to the relevant sections of the documentation here also for completeness:
- duration here
- convexity here
Answers to the additional points raised in the edited question:
- There is no difference between providing a list of cash flows verus a `Leg`. In the underlying C++ code, the `QuantLib::Leg` is simply a `typedef` of a vector of cashflows here.
- The easiest way to understand the logic here is to look at the underlying C++ code here. If `settlementDate` is an empty date, it is taken as the evaluation date i.e. `Settings::instance().evaluationDate()` in C++. If the `npvDate` is empty, it is taken as the `settlementDate`, after the adjustment in the previous sentence if it was necessary.Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.