Financial Math Submodule
Provides a set of common routines in financial maths.
Quickstart
cfs = [5, 5, 105]
times = [1, 2, 3]
discount_rate = 0.03
present_value(discount_rate, cfs, times) # 105.65
duration(Macaulay(), discount_rate, cfs, times) # 2.86
duration(discount_rate, cfs, times) # 2.78
convexity(discount_rate, cfs, times) # 10.62API
Exported API
ActuaryUtilities.FinancialMath.CS01 — Type
CS01 <: DurationCredit 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.
Requires both a base curve and credit spread to be specified. For a flat additive decomposition, CS01 ≈ IR01 ≈ DV01.
ActuaryUtilities.FinancialMath.DV01 — Type
DV01 <: DurationDollar Value of 01. The dollar change in value for a 1 basis point (0.01%) parallel shift in rates.
DV01 = -∂V/∂r / 10000, so a DV01 of 0.045 means the position loses 0.045 per 100 notional for a 1bp rate increase.
ActuaryUtilities.FinancialMath.IR01 — Type
IR01 <: DurationInterest 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.
Requires both a base curve and credit spread to be specified. For a flat additive decomposition, IR01 ≈ CS01 ≈ DV01.
ActuaryUtilities.FinancialMath.KeyRate — Type
KeyRate(timepoints,shift=0.001)A convenience constructor for KeyRateZero.
Extended Help
KeyRateZero is chosen as the default constructor because it has more attractive properties than KeyRatePar:
- rates after the key
timepointremain unaffected by theshift- e.g. this causes a 6-year zero coupon bond would have a negative duration if the 5-year par rate was used
ActuaryUtilities.FinancialMath.KeyRatePar — Type
KeyRatePar(timepoint,shift=0.001) <: KeyRateDurationShift the par curve by the given amount at the given timepoint. Use in conjunction with duration to calculate the key rate duration.
Unlike other duration statistics which are computed using analytic derivatives, KeyRateDurations are computed via a shift-and-compute the yield curve approach.
KeyRatePar is more commonly reported (than KeyRateZero) in the fixed income markets, even though the latter has more analytically attractive properties. See the discussion of KeyRateDuration in the FinanceModels.jl docs.
ActuaryUtilities.FinancialMath.KeyRateZero — Type
KeyRateZero(timepoint,shift=0.001) <: KeyRateDurationShift the par curve by the given amount at the given timepoint. Use in conjunction with duration to calculate the key rate duration.
Unlike other duration statistics which are computed using analytic derivatives, KeyRateDuration is computed via a shift-and-compute the yield curve approach.
KeyRateZero is less commonly reported (than KeyRatePar) in the fixed income markets, even though the latter has more analytically attractive properties. See the discussion of KeyRateDuration in the FinanceModels.jl docs.
ActuaryUtilities.FinancialMath.KeyRates — Type
KeyRates <: DurationDispatch type that requests the full key-rate decomposition (vector of durations or matrix of convexities) instead of the default scalar summary.
Use with duration and convexity when a ZeroRateCurve is the rate input:
duration(KeyRates(), zrc, cfs, times) # vector of key rate durations
duration(DV01(), KeyRates(), zrc, cfs, times) # vector of key rate DV01s
convexity(KeyRates(), zrc, cfs, times) # matrix of key rate convexitiesWithout KeyRates(), these functions return a scalar (the sum of the decomposition).
ActuaryUtilities.FinancialMath.breakeven — Function
breakeven(yield, cashflows::Vector)
breakeven(yield, cashflows::Vector,times::Vector)Calculate the time when the accumulated cashflows breakeven given the yield.
Assumptions:
- cashflows occur at the end of the period
- cashflows evenly spaced with the first one occuring at time zero if
timesnot given
Returns nothing if cashflow stream never breaks even.
julia> breakeven(0.10, [-10,1,2,3,4,8])
5
julia> breakeven(0.10, [-10,15,2,3,4,8])
1
julia> breakeven(0.10, [-10,-15,2,3,4,8]) # returns the `nothing` value
ActuaryUtilities.FinancialMath.convexity — Method
convexity(yield,cfs,times)
convexity(yield,valuation_function)Calculates the convexity. - yield should be a fixed effective yield (e.g. 0.05). - times may be omitted and it will assume cfs are evenly spaced beginning at the end of the first period.
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.854368932038834
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
ActuaryUtilities.FinancialMath.convexity — Method
convexity(base::ZeroRateCurve, credit::ZeroRateCurve, cfs, times) -> NamedTuple of scalarsCompute scalar two-curve convexity. Returns a NamedTuple with scalar base, credit, and cross values (sums of the corresponding key rate matrices).
For the full key-rate decomposition (matrices), use KeyRates().
ActuaryUtilities.FinancialMath.convexity — Method
convexity(zrc::ZeroRateCurve, cfs, times) -> scalar
convexity(valuation_fn::Function, zrc::ZeroRateCurve) -> scalarCompute the scalar convexity for a ZeroRateCurve: the sum of all elements of the key rate convexity matrix.
For the full key-rate decomposition (a matrix), use KeyRates():
convexity(KeyRates(), zrc, cfs, times) # matrix
convexity(zrc, cfs, times) # scalar (≡ sum of above)ActuaryUtilities.FinancialMath.convexity — Method
convexity(::KeyRates, base::ZeroRateCurve, credit::ZeroRateCurve, cfs, times) -> NamedTuple of matricesCompute two-curve convexity. Returns a NamedTuple with base, credit, and cross matrices.
ActuaryUtilities.FinancialMath.convexity — Method
convexity(::KeyRates, zrc::ZeroRateCurve, cfs, times) -> Matrix
convexity(::KeyRates, valuation_fn::Function, zrc::ZeroRateCurve) -> MatrixCompute key rate convexity matrix: ∂²V/∂rᵢ∂rⱼ / V.
Examples
using FinanceModels
zrc = ZeroRateCurve([0.03, 0.03, 0.03], [1.0, 2.0, 3.0])
# Key rate convexity matrix
conv = convexity(KeyRates(), zrc, [5.0, 5.0, 105.0], [1.0, 2.0, 3.0])
# Scalar convexity
convexity(zrc, [5.0, 5.0, 105.0], [1.0, 2.0, 3.0]) # ≡ sum(conv)ActuaryUtilities.FinancialMath.moic — Method
moic(cashflows<:AbstractArray)The multiple on invested capital ("moic") is the un-discounted sum of distributions divided by the sum of the contributions. The function assumes that negative numbers in the array represent contributions and positive numbers represent distributions.
Examples
julia> moic([-10,20,30])
5.0ActuaryUtilities.FinancialMath.present_values — Function
present_values(interest, cashflows, timepoints)Efficiently calculate a vector representing the present value of the given cashflows at each period prior to the given timepoint.
Examples
julia> present_values(0.00, [1,1,1])
[3,2,1]
julia> present_values(ForwardYield([0.1,0.2]), [10,20],[0,1]) # after `using FinanceModels`
2-element Vector{Float64}:
28.18181818181818
18.18181818181818ActuaryUtilities.FinancialMath.price — Method
price(...)The absolute value of the present_value(...).
Extended help
Using price can be helpful if the directionality of the value doesn't matter. For example, in the common usage, duration is more interested in the change in price than present value, so price is used there.
ActuaryUtilities.FinancialMath.sensitivities — Method
sensitivities(valuation_fn, base::ZeroRateCurve, credit::ZeroRateCurve)
sensitivities(base::ZeroRateCurve, credit::ZeroRateCurve, cfs, times)Two-curve sensitivities. Returns base/credit durations and convexity matrices.
For DV01s instead of durations, use sensitivities(DV01(), base, credit, cfs, times).
The convexities.cross matrix [i,j] = ∂²V/(∂base_rᵢ ∂credit_rⱼ) / V captures interaction effects between base and credit rate movements — relevant when the two curves move in correlated fashion (e.g., both driven by macro factors).
ActuaryUtilities.FinancialMath.sensitivities — Method
sensitivities(zrc::ZeroRateCurve, valuation_fn::Function)
sensitivities(zrc::ZeroRateCurve, cfs, times)Compute value, key rate durations, and convexity matrix in a single efficient AD pass.
Always returns the full key-rate decomposition (vectors and matrices), equivalent to the KeyRates() dispatch of duration and convexity. Use duration(zrc, ...) or convexity(zrc, ...) directly if you only need scalar summaries.
Returns a NamedTuple with:
value: the scalar present valuedurations: modified key rate durations (-∂V/∂rᵢ / V) — vectorconvexities: cross-convexity matrix (∂²V/∂rᵢ∂rⱼ / V) — matrix
For DV01s instead of durations, use sensitivities(DV01(), zrc, cfs, times).
Supports do-block syntax:
using FinanceModels
zrc = ZeroRateCurve([0.03, 0.03, 0.03], [1.0, 2.0, 3.0])
result = sensitivities(zrc) do curve
sum(cf * curve(t) for (cf, t) in zip([5.0, 5.0, 105.0], [1.0, 2.0, 3.0]))
endWhen using stochastic (Monte Carlo) valuations, you must fix the RNG seed so that the same random draws are used for every AD perturbation:
result = sensitivities(zrc) do curve
hw = HullWhite(0.1, 0.01, curve)
pv_mc(hw, contract; n_scenarios=1000, rng=MersenneTwister(42))
endWithout a fixed seed, gradients will be noisy and incorrect.
Pathwise AD is invalid for discontinuous payoffs (digital options, barriers). For those cases, use finite differences instead.
To obtain traditional scalar sensitivities from the results, sum the vector/matrix fields:
result = sensitivities(zrc, cfs, [1.0, 2.0, 3.0])
sum(result.durations) # scalar modified duration
sum(result.convexities) # scalar convexityActuaryUtilities.FinancialMath.spread — Function
spread(curve1,curve2,cashflows)Return the solved-for constant spread to add to curve1 in order to equate the discounted cashflows with curve2
Examples
spread(0.04, 0.05, cfs)
Rate{Float64, Periodic}(0.010000000000000009, Periodic(1))ActuaryUtilities.duration — Method
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:
- Quant Finance Stack Exchange: To compute key rate duration, shall I use par curve or zero curve?
- (Financial Exam Help 123](http://www.financialexamhelp123.com/key-rate-duration/)
ActuaryUtilities.duration — Method
duration(::CS01, base::ZeroRateCurve, credit::ZeroRateCurve, cfs, times) -> scalarCompute scalar CS01 for a two-curve valuation. For key-rate decomposition, use KeyRates().
ActuaryUtilities.duration — Method
duration(::CS01, ::KeyRates, base::ZeroRateCurve, credit::ZeroRateCurve, cfs, times) -> VectorCompute key rate DV01s for the credit spread curve: -∂V/∂credit_rᵢ / 10000.
ActuaryUtilities.duration — Method
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)
trueActuaryUtilities.duration — Method
duration(::DV01, zrc::ZeroRateCurve, cfs, times) -> scalar
duration(::DV01, valuation_fn::Function, zrc::ZeroRateCurve) -> scalarCompute 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)ActuaryUtilities.duration — Method
duration(::DV01, ::KeyRates, zrc::ZeroRateCurve, cfs, times) -> Vector
duration(::DV01, ::KeyRates, valuation_fn::Function, zrc::ZeroRateCurve) -> VectorCompute key rate DV01s as a vector: -∂V/∂rᵢ / 10000 for each tenor.
ActuaryUtilities.duration — Method
duration(zrc::ZeroRateCurve, cfs, times) -> scalar
duration(valuation_fn::Function, zrc::ZeroRateCurve) -> scalarCompute 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)ActuaryUtilities.duration — Method
duration(::IR01, base::ZeroRateCurve, credit::ZeroRateCurve, cfs, times) -> scalarCompute scalar IR01 for a two-curve valuation. For key-rate decomposition, use KeyRates().
ActuaryUtilities.duration — Method
duration(::IR01, ::KeyRates, base::ZeroRateCurve, credit::ZeroRateCurve, cfs, times) -> VectorCompute key rate DV01s for the base (risk-free) curve: -∂V/∂base_rᵢ / 10000.
ActuaryUtilities.duration — Method
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)
trueActuaryUtilities.duration — Method
duration(::KeyRates, zrc::ZeroRateCurve, cfs, times) -> Vector
duration(::KeyRates, valuation_fn::Function, zrc::ZeroRateCurve) -> VectorCompute 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]))
endActuaryUtilities.duration — Method
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 DurationCalculates 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