Utilities submodule

Provides miscellaneous routines common in actuarial and financial work.

API

Exported API

ActuaryUtilities.durationFunction
duration(d1::Date, d2::Date)

Compute the duration given two dates, which is the number of years since the first date. The interval [0,1) is defined as having duration 1. Can return negative durations if second argument is before the first.

julia> issue_date  = Date(2018,9,30);

julia> duration(issue_date , Date(2019,9,30) ) 
2
julia> duration(issue_date , issue_date) 
1
julia> duration(issue_date , Date(2018,10,1) ) 
1
julia> duration(issue_date , Date(2019,10,1) ) 
2
julia> duration(issue_date , Date(2018,6,30) ) 
0
julia> duration(Date(2018,9,30),Date(2017,6,30)) 
-1
source
duration(Macaulay(),interest_rate,cfs,times)
duration(Modified(),interest_rate,cfs,times)
duration(DV01(),interest_rate,cfs,times)
duration(IR01(),base_curve,credit_spread,cfs,times)
duration(CS01(),base_curve,credit_spread,cfs,times)
duration(interest_rate,cfs,times)             # Modified Duration
duration(interest_rate,valuation_function)    # Modified Duration

Calculates the Macaulay, Modified, DV01, IR01, or CS01 duration. times may be ommitted and the valuation will assume evenly spaced cashflows starting at the end of the first period.

Note that the calculated duration will depend on the periodicity convention of the interest_rate: a Periodic yield (or yield model with that convention) will be a slightly different computed duration than a Continous which follows from the present value differing according to the periodicity.

When not given Modified() or Macaulay() as an argument, will default to Modified().

  • Modified duration: the relative change per point of yield change.
  • Macaulay: the cashflow-weighted average time.
  • DV01: the absolute change per basis point (hundredth of a percentage point).
  • IR01: the absolute change per basis point shift in the risk-free (base) curve, holding credit spread constant.
  • CS01: the absolute change per basis point shift in the credit spread, holding the risk-free (base) curve constant.

Examples

Using vectors of cashflows and times

julia> times = 1:5;

julia> cfs = [0,0,0,0,100];

julia> duration(0.03,cfs,times)
4.854368932038835

julia> duration(Periodic(0.03,1),cfs,times)
4.854368932038835

julia> duration(Continuous(0.03),cfs,times)
5.0

julia> duration(Macaulay(),0.03,cfs,times)
5.0

julia> duration(Modified(),0.03,cfs,times)
4.854368932038835

julia> convexity(0.03,cfs,times)
28.277877274012614

Using any given value function:

julia> lump_sum_value(amount,years,i) = amount / (1 + i ) ^ years
julia> my_lump_sum_value(i) = lump_sum_value(100,5,i)
julia> duration(0.03,my_lump_sum_value)
4.854368932038835
julia> convexity(0.03,my_lump_sum_value)
28.277877274012617
source
duration(IR01(), base_curve, credit_spread, cfs, times)
duration(IR01(), base_curve, credit_spread, cfs)

Calculate the IR01 (Interest Rate 01): the dollar change in value for a 1 basis point parallel shift in the risk-free (base) curve, holding the credit spread constant.

The total discount rate is assumed to be base_curve + credit_spread. For a flat additive decomposition (e.g. scalar rates), IR01 ≈ CS01 ≈ DV01.

Examples

julia> cfs = [5, 5, 5, 105];

julia> times = 1:4;

julia> duration(IR01(), 0.03, 0.02, cfs, times)
0.03465054893498076

julia> duration(IR01(), 0.03, 0.02, cfs, times) ≈ duration(DV01(), 0.05, cfs, times)
true
source
duration(CS01(), base_curve, credit_spread, cfs, times)
duration(CS01(), base_curve, credit_spread, cfs)

Calculate the CS01 (Credit Spread 01): the dollar change in value for a 1 basis point parallel shift in the credit spread, holding the risk-free (base) curve constant.

The total discount rate is assumed to be base_curve + credit_spread. For a flat additive decomposition (e.g. scalar rates), CS01 ≈ IR01 ≈ DV01.

Examples

julia> cfs = [5, 5, 5, 105];

julia> times = 1:4;

julia> duration(CS01(), 0.03, 0.02, cfs, times)
0.03465054893498076

julia> duration(CS01(), 0.03, 0.02, cfs, times) ≈ duration(DV01(), 0.05, cfs, times)
true
source
duration(keyrate::KeyRateDuration,curve,cashflows)    
duration(keyrate::KeyRateDuration,curve,cashflows,timepoints)
duration(keyrate::KeyRateDuration,curve,cashflows,timepoints,krd_points)

Calculate the key rate duration by shifting the zero (not par) curve by the kwarg shift at the timepoint specified by a KeyRateDuration(time).

The approach is to carve up the curve into krd_points (default is the unit steps between 1 and the last timepoint of the casfhlows). The zero rate corresponding to the timepoint within the KeyRateDuration is shifted by shift (specified by the KeyRateZero or KeyRatePar constructors. A new curve is created from the shifted rates. This means that the "width" of the shifted section is ± 1 time period, unless specific points are specified via krd_points.

The curve may be any FinanceModels.jl curve (e.g. does not have to be a curve constructed via FinanceModels.Zero(...)).

!!! Experimental: Due to the paucity of examples in the literature, this feature does not have unit tests like the rest of JuliaActuary functionality. Additionally, the API may change in a future major/minor version update.

Examples

julia> riskfree_maturities = [0.5, 1.0, 1.5, 2.0];

julia> riskfree    = [0.05, 0.058, 0.064,0.068];

julia> rf_curve = FinanceModels.Zero(riskfree,riskfree_maturities);

julia> cfs = [10,10,10,10,10];

julia> duration(KeyRate(1),rf_curve,cfs)
8.932800152336995

Extended Help

Key Rate Duration is not a well specified topic in the literature and in practice. The reference below suggest that shocking the par curve is more common in practice, but that the zero curve produces more consistent results. Future versions may support shifting the par curve.

References:

source
duration(zrc::ZeroRateCurve, cfs, times) -> scalar
duration(valuation_fn::Function, zrc::ZeroRateCurve) -> scalar

Compute the scalar modified duration for a ZeroRateCurve: the sum of all key rate durations.

For the full key-rate decomposition (a vector), use KeyRates():

duration(KeyRates(), zrc, cfs, times)   # vector
duration(zrc, cfs, times)               # scalar (≡ sum of above)
source
duration(::KeyRates, zrc::ZeroRateCurve, cfs, times) -> Vector
duration(::KeyRates, valuation_fn::Function, zrc::ZeroRateCurve) -> Vector

Compute key rate durations (modified) as a vector: -∂V/∂rᵢ / V for each tenor.

When called with a function, it receives a curve and returns a scalar value (do-block syntax).

Examples

using FinanceModels
zrc = ZeroRateCurve([0.03, 0.03, 0.03], [1.0, 2.0, 3.0])
cfs = [5.0, 5.0, 105.0]

# Key rate durations (vector)
krds = duration(KeyRates(), zrc, cfs, [1.0, 2.0, 3.0])

# Scalar modified duration
duration(zrc, cfs, [1.0, 2.0, 3.0])   # ≡ sum(krds)

# Do-block for custom valuation
krds = duration(KeyRates(), zrc) do curve
    sum(cf * curve(t) for (cf, t) in zip(cfs, [1.0, 2.0, 3.0]))
end
source
duration(::DV01, zrc::ZeroRateCurve, cfs, times) -> scalar
duration(::DV01, valuation_fn::Function, zrc::ZeroRateCurve) -> scalar

Compute the scalar DV01 for a ZeroRateCurve: the sum of all key rate DV01s.

For the full key-rate decomposition (a vector), use KeyRates():

duration(DV01(), KeyRates(), zrc, cfs, times)   # vector
duration(DV01(), zrc, cfs, times)                # scalar (≡ sum of above)
source
duration(::DV01, ::KeyRates, zrc::ZeroRateCurve, cfs, times) -> Vector
duration(::DV01, ::KeyRates, valuation_fn::Function, zrc::ZeroRateCurve) -> Vector

Compute key rate DV01s as a vector: -∂V/∂rᵢ / 10000 for each tenor.

source
duration(::IR01, base::ZeroRateCurve, credit::ZeroRateCurve, cfs, times) -> scalar

Compute scalar IR01 for a two-curve valuation. For key-rate decomposition, use KeyRates().

source
duration(::IR01, ::KeyRates, base::ZeroRateCurve, credit::ZeroRateCurve, cfs, times) -> Vector

Compute key rate DV01s for the base (risk-free) curve: -∂V/∂base_rᵢ / 10000.

source
duration(::CS01, base::ZeroRateCurve, credit::ZeroRateCurve, cfs, times) -> scalar

Compute scalar CS01 for a two-curve valuation. For key-rate decomposition, use KeyRates().

source
duration(::CS01, ::KeyRates, base::ZeroRateCurve, credit::ZeroRateCurve, cfs, times) -> Vector

Compute key rate DV01s for the credit spread curve: -∂V/∂credit_rᵢ / 10000.

source
ActuaryUtilities.Utilities.years_betweenFunction
Years_Between(d1::Date, d2::Date)

Compute the number of integer years between two dates, with the first date typically before the second. Will return negative number if first date is after the second. Use third argument to indicate if calendar anniversary should count as a full year.

Examples

julia> d1 = Date(2018,09,30);

julia> d2 = Date(2019,09,30);

julia> d3 = Date(2019,10,01);

julia> years_between(d1,d3) 
1
julia> years_between(d1,d2,false) # same month/day but `false` overlap
0 
julia> years_between(d1,d2) # same month/day but `true` overlap
1 
julia> years_between(d1,d2) # using default `true` overlap
1 
source
ActuaryUtilities.Utilities.accum_offsetFunction
accum_offset(x; op=*, init=1.0)

A shortcut for the common operation wherein a vector is scanned with an operation, but has an initial value and the resulting array is offset from the traditional accumulate.

This is a common pattern when calculating things like survivorship given a mortality vector and you want the first value of the resulting vector to be 1.0, and the second value to be 1.0 * x[1], etc.

Two keyword arguments:

  • op is the binary (two argument) operator you want to use, such as * or +
  • init is the initial value in the returned array

Examples

julia> accum_offset([0.9, 0.8, 0.7])
3-element Array{Float64,1}:
 1.0
 0.9
 0.7200000000000001

julia> accum_offset(1:5) # the product of elements 1:n, with the default `1` as the first value
5-element Array{Int64,1}:
  1
  1
  2
  6
 24

julia> accum_offset(1:5,op=+)
5-element Array{Int64,1}:
  1
  2
  4
  7
 11
source