Financial Math Submodule
Provides a set of common routines in financial maths.
Quickstart
using ActuaryUtilities
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.62Macaulay/Modified duration are for fixed cashflows. For a floating-rate bond, a portfolio, or any curve-dependent contract, pass the contract directly — duration(Effective(), contract, curve, tenors) / duration(Spread(), …) / dv01(…) re-project the coupons and give the rate-vs-spread durations (years and DV01s); sensitivities(contract, curve, tenors) returns the full bundle, and sensitivities(contract, tenors; discount = (; rf, credit, ilp), index = …) does the multi-curve decomposition. See Key Rate Sensitivities.
Curve Transformations
FinanceModels.Yield.TenorShift lets you lazily transform any yield curve's zero rates via curve + (z, t) -> new_rate. This is useful for scenario analysis (parallel shifts, twists, stresses) without refitting.
Parallel shift
using ActuaryUtilities, FinanceModels, FinanceCore
base = Yield.Constant(Continuous(0.05))
shifted = base + (z, t) -> z + Continuous(0.01) # +100 bp parallel
zero(shifted, 1.0)Continuous(0.060000000000000005)Periodic rate arithmetic
Rate arithmetic automatically handles compounding conversion — a Periodic(0.01, 1) bump converts to continuous internally:
shifted_p = base + (z, t) -> z + Periodic(0.01, 1)
zero(shifted_p, 5.0) # ≈ Continuous(0.05 + log(1.01))Continuous(0.059950330853168095)Tenor-dependent twist
A steepener that fades at 30y:
twist = base + (z, t) -> z + Continuous(0.02 * max(0.0, 1.0 - t / 30.0))
(zero(twist, 1.0), zero(twist, 15.0), zero(twist, 30.0))(Continuous(0.06933333333333333), Continuous(0.060000000000000005), Continuous(0.05))PV comparison under stress
cfs = [5.0, 5.0, 5.0, 105.0]
times = [1.0, 2.0, 3.0, 4.0]
pv_base = present_value(base, cfs, times)
pv_shifted = present_value(shifted, cfs, times)
pct_change = (pv_shifted - pv_base) / pv_base * 100
(; pv_base = round(pv_base, digits=4), pv_shifted = round(pv_shifted, digits=4), pct_change = round(pct_change, digits=2))(pv_base = 99.5506, pv_shifted = 95.9157, pct_change = -3.65)Bootstrapped curve + stress
quotes = ZCBYield.([0.04, 0.05, 0.055, 0.06], [1.0, 3.0, 5.0, 10.0])
fitted = fit(Spline.Linear(), quotes, Fit.Bootstrap())
stressed = fitted + (z, t) -> z + Continuous(0.005) # +50 bp
dur_base = duration(fitted, cfs, times)
dur_stressed = duration(stressed, cfs, times)
(; dur_base = round(dur_base, digits=4), dur_stressed = round(dur_stressed, digits=4))(dur_base = 3.719, dur_stressed = 3.716)Negative rates
Real-world EUR/JPY/CHF scenarios with negative base rates:
neg = Yield.Constant(Continuous(-0.01))
shifted_neg = neg + (z, t) -> z + Continuous(0.005)
(; rate = zero(shifted_neg, 5.0), df = round(discount(shifted_neg, 5.0), digits=6))(rate = Continuous(-0.005), df = 1.025315)API
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.Effective — Type
Effective <: DurationEffective (rate) duration / convexity for a curve-dependent contract (e.g. a floating-rate bond): reprices under a shifted curve with the projected cashflows RE-COMPUTED, so a floating coupon re-fixes. The correct interest-rate duration for floating-rate instruments; Modified/Macaulay are valid only for curve-independent (fixed) cashflows. duration(Effective(), contract, curve, tenors).
See also: Spread, sensitivities, locked_floater.
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. shifting the 5-year par rate would (incorrectly) give a 6-year zero coupon bond a negative key rate duration, while a 5-year zero-rate shift leaves it unaffected
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 KeyRateZero 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 zero 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 zero-curve shifts have more analytically attractive properties (rates beyond the shifted timepoint are unaffected). See the discussion of KeyRateDuration in the FinanceModels.jl docs.
ActuaryUtilities.FinancialMath.KeyRates — Type
KeyRates(tenors) <: DurationMarker type carrying the key-rate knot grid tenors for use with duration, convexity, and sensitivities. Requests the full key-rate decomposition (vector of durations, matrix of convexities) instead of the default scalar summary.
tenors is any AbstractVector{<:Real} of positive knot times. The knot grid is carried with the measurement intent — "key rate durations at these tenors" lives in one object.
tenors = [1.0, 2.0, 5.0, 10.0, 30.0]
duration(KeyRates(tenors), curve, cfs, times) # vector of key rate durations
duration(DV01(), KeyRates(tenors), curve, cfs, times) # vector of key rate DV01s
convexity(KeyRates(tenors), curve, cfs, times) # matrix of key rate convexities
sensitivities(KeyRates(tenors), curve, cfs, times) # value + durations + convexitiesActuaryUtilities.FinancialMath.Spread — Type
Spread <: DurationSpread (credit) duration: bumps the discount curve only, holding the projected (index) cashflows fixed. For a floating-rate bond this is ≈ time to maturity — the discount-margin / credit sensitivity.
See also: Effective, sensitivities.
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.277877274012635
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.277877274012642
ActuaryUtilities.FinancialMath.dv01 — Method
dv01(args...)Dollar value of a 1bp move. dv01(args...) ≡ duration(DV01(), args...) for the cashflow/curve forms, with dv01(Effective()/Spread(), target, [forward, credit,] tenors) giving the floating-rate dollar durations (years × value ÷ 10⁴).
ActuaryUtilities.FinancialMath.locked_floater — Method
locked_floater(fl::FinanceModels.Bond.Floating, current_coupon, next_reset)Model an in-force floater whose current coupon is LOCKED at current_coupon (the per-period amount fixed at the last reset) until next_reset, after which it floats. A Composite of a coupon-only stub at next_reset plus a forward-starting floater for the remainder (principal rides the forward leg). Gives the conventional rate duration ≈ time to next reset; without it the idealized effective duration is ≈ 0 at a reset.
The remaining term fl.maturity - next_reset must be an integer number of coupon periods. Otherwise the forward-starting leg would carry a stub first coupon whose fix-in-advance reference rate looks back before time zero — a quietly mispriced quantity on curves that extrapolate below $t = 0$ and a DomainError on ZeroRateCurve — so non-commensurate inputs throw an ArgumentError.
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-element Vector{Float64}:
3.0
2.0
1.0
julia> present_values(0.05, [10,10,110], [1,2,3])
3-element Vector{Float64}:
113.61624014685238
109.297052154195
104.76190476190476ActuaryUtilities.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.reproject — Method
reproject(contract, index_curve)Wrap contract so its coupons are estimated off index_curve: returns the contract itself if it reads no model, else a Projection mapping the contract's keys to index_curve. Lets a multi-curve valuation avoid hand-writing the model Dict.
ActuaryUtilities.FinancialMath.sensitivities — Method
sensitivities(valuation, curves::NamedTuple; tenors) -> (; value, duration, dv01, key_rate)
sensitivities(target, tenors; discount::NamedTuple, index) -> sameMulti-curve sensitivities: differentiate valuation(curves) w.r.t. each named curve in curves in a single AD pass, returning a per-role duration/dv01/key_rate NamedTuple. The structured form assembles discount = sum(discount layers) and projects the contract's coupons on index — e.g. discount = (; rf, credit, ilp) gives r.duration.rf (≈ IR01), .credit (≈ CS01), .ilp ("ILP01"), and .index (the reset sensitivity). ILP / matching-adjustment / basis are just additional named curves.
ActuaryUtilities.FinancialMath.sensitivities — Method
sensitivities(kr::KeyRates, valuation_fn, curve::AbstractYieldModel) -> NamedTuple
sensitivities(kr::KeyRates, curve::AbstractYieldModel, cfs, times) -> NamedTuple
sensitivities(::DV01, kr::KeyRates, valuation_fn, curve::AbstractYieldModel) -> NamedTuple
sensitivities(kr::KeyRates, base::AbstractYieldModel, credit::AbstractYieldModel, cfs, times) -> NamedTuple
sensitivities(::DV01, kr::KeyRates, base, credit, cfs, times) -> NamedTupleBundled value + key-rate durations (or DV01s) + convexity matrix for any AbstractYieldModel or pair, in a single AD pass. The knot grid is carried by KeyRates.
ActuaryUtilities.FinancialMath.sensitivities — Method
sensitivities(target, curve, tenors) -> NamedTuple
sensitivities(target, forward, credit, tenors) -> NamedTupleOne-AD-pass bundle for a (possibly curve-dependent) target — a FinanceModels contract or a vector of contracts (portfolio) — re-projecting cashflows under bumped curves. Coupons are estimated on forward, discounted on credit (pass a single curve for both). Returns, over the tenors key-rate grid:
valueeffective_duration/effective_dv01/effective_key_rate— bump both curves (coupons re-fix): the interest-rate duration (≈ next reset for a floater).spread_duration/spread_dv01/spread_key_rate— bump the discount only: the discount-margin / credit duration (≈ maturity for a floater).forward_duration/forward_dv01/forward_key_rate— bump the index only;effective = forward + spread(first order).
Durations in years; DV01s in dollars per 1bp. For a fixed bond effective == spread == the modified duration and forward == 0. See duration with Effective/ Spread, dv01, zspread, locked_floater.
ActuaryUtilities.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
The spread is found via a damped Newton iteration on the pricing residual and is solved to machine precision; an ErrorException is thrown if the solve does not converge within maxiter iterations.
For mixed-sign cashflows the pricing residual can have more than one exact root (e.g. a duration-neutral asset/liability pair); the root reached from a starting spread of zero is returned.
Examples
julia> spread(0.04, 0.05, fill(10.0, 10))
Periodic(0.010000000000000009, 1)ActuaryUtilities.FinancialMath.zspread — Method
zspread(contract, credit, market_price; forward=credit) -> (; zspread, zspread_dv01)Constant continuously-compounded spread s on the credit (discount) curve such that the model price equals market_price, with coupons estimated on forward (held fixed). Returns the spread and its sensitivity ($/1bp parallel move of credit + s). Newton + AD.
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(...)).
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_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.035459505041623596
julia> duration(CS01(), 0.03, 0.02, cfs, times) ≈ duration(DV01(), 0.05, cfs, times)
trueActuaryUtilities.duration — Method
duration(::DV01, valuation_fn, curve::AbstractYieldModel, tenors) -> scalar
duration(::DV01, curve::AbstractYieldModel, tenors, cfs, times) -> scalar
duration(::DV01, kr::KeyRates, valuation_fn, curve::AbstractYieldModel) -> Vector
duration(::DV01, kr::KeyRates, curve::AbstractYieldModel, cfs, times) -> VectorDV01 (scalar or per-knot vector) for any AbstractYieldModel. Equivalent to the KeyRates variants of duration but in dollars per basis point.
ActuaryUtilities.duration — Method
duration(Effective(), target, curve, tenors) # rate duration, yrs
duration(Spread(), target, curve, tenors) # spread duration, yrs
duration(Effective(), KeyRates(tenors), target, curve) # key-rate vector
dv01(Effective()/Spread(), target, curve, tenors) # the dollar versionsEffective (rate) and spread (credit) duration / DV01 for a contract or portfolio, re-projecting cashflows under bumped curves. Two-curve forms take (forward, credit). See sensitivities for the full one-pass bundle.
ActuaryUtilities.duration — Method
duration(valuation_fn, curve::AbstractYieldModel, tenors) -> scalar
duration(curve::AbstractYieldModel, tenors, cfs, times) -> scalar
duration(curve::AbstractYieldModel, tenors, cfs::AbstractVector{<:Cashflow}) -> scalarScalar modified duration for any AbstractYieldModel evaluated against a KRD knot grid. Equivalent to sum(duration(KeyRates(tenors), ...)).
Use KeyRates to obtain the per-knot vector decomposition.
Example
duration(pv, my_composite_curve, [0.25, 1, 5, 10, 30])ActuaryUtilities.duration — Method
duration(::IR01, valuation_fn, base::AbstractYieldModel, credit::AbstractYieldModel, tenors) -> scalar
duration(::IR01, base::AbstractYieldModel, credit::AbstractYieldModel, tenors, cfs, times) -> scalar
duration(::IR01, kr::KeyRates, valuation_fn, base, credit) -> Vector
duration(::IR01, kr::KeyRates, base, credit, cfs, times) -> Vector
duration(::CS01, ...) -> ...Two-curve IR01/CS01 for any AbstractYieldModel pair sharing a tenor grid. IR01 bumps the base (risk-free) curve only; CS01 bumps the credit (spread) curve only.
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.035459505041623596
julia> duration(IR01(), 0.03, 0.02, cfs, times) ≈ duration(DV01(), 0.05, cfs, times)
trueActuaryUtilities.duration — Method
duration(kr::KeyRates, valuation_fn, curve::AbstractYieldModel) -> Vector
duration(kr::KeyRates, curve::AbstractYieldModel, cfs, times) -> Vector
duration(kr::KeyRates, curve::AbstractYieldModel, cfs::AbstractVector{<:Cashflow}) -> VectorKey-rate durations (modified) for any AbstractYieldModel, computed by layering a triangular-hat zero-rate bump at each tenor in kr.tenors over the user's curve via Yield.TenorShift, then taking the AD gradient w.r.t. the bump magnitudes. The user's curve is preserved at all non-knot points.
Tenor grid
kr.tenors is the KRD knot grid — a separate modeling choice from any tenor structure baked into the curve itself. You can evaluate key-rate durations on any grid (e.g. Bloomberg {0.25, 1, 2, 5, 10, 30}, FRTB {0.25, 0.5, 1, 2, 3, 5, 10, 15, 20, 30}, etc.) without re-fitting the underlying curve.
The grid must be sorted ascending, distinct, and strictly positive. These preconditions are not checked at runtime — a malformed grid produces wrong gradients silently.
Bump shape and endpoint extrapolation
The bump at the i-th knot is a triangular hat centered at tenors[i] with support [tenors[i-1], tenors[i+1]]. Outside the knot range it is flat: bumping tenors[1] perturbs all cashflows at t ≤ tenors[1] equally, and bumping tenors[end] perturbs all cashflows at t ≥ tenors[end] equally. For long-duration insurance liabilities (LTC, deferred / payout annuities), the last-knot KRD absorbs all super-tenor sensitivity — extend the grid past your longest cashflow if you want that decomposed.
For a linearly-interpolated zero-rate curve the result matches AD over the curve's own rates exactly. For other splines the bump kernel is hat-shaped rather than spline-shaped, so per-knot KRDs shift slightly; the sum of KRDs (= scalar modified duration) is invariant either way.
Example
duration(KeyRates([0.25, 1, 5, 10, 30]), pv, curve)
duration(KeyRates([0.25, 1, 5, 10, 30]), curve) do c
pv(c)
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.
cfs can be an AbstractVector{<:Cashflow} (from FinanceCore), in which case times is extracted automatically and should be omitted.
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.
Periodicity convention
The Modified duration returned depends on the space in which the parallel rate shock is applied, and this differs between plain rates and yield models:
- A scalar (e.g.
0.04) or aRateis shocked in its own compounding space. A scalar is treated asPeriodic(0.04, 1), so Modified = Macaulay / (1 + 0.04); in general aPeriodic(y, m)rate gives Modified = Macaulay / (1 + y/m), and aContinuous(y)rate gives Modified = Macaulay. - A yield model (e.g.
Yield.Constant(0.04)from FinanceModels) composes the shock in continuous-zero space, so Modified = Macaulay under the curve's own discounting, regardless of the compounding convention stored in the model.
The same inputs therefore produce two different numbers by design:
julia> times = 1:5; cfs = [0,0,0,0,100];
julia> duration(0.04, cfs, times) # Periodic(1) shock: Macaulay / 1.04
4.8076923076923075
julia> duration(Yield.Constant(0.04), cfs, times) # continuous-zero shock: Macaulay
5.0Examples
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.277877274012635
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.277877274012642
Unexported API
ActuaryUtilities.FinancialMath._tent_bump — Method
_tent_bump(shift, τ, krd_points)Return a closure (z, t) -> z + Continuous(bump) implementing the Ho (1992) tent function for key-rate duration bump-and-reprice:
- First KRD point: flat
shiftfort ≤ τ, linear ramp to 0 at next neighbor. - Last KRD point: linear ramp from 0 at previous neighbor, flat
shiftfort ≥ τ. - Interior: triangle with peak
shiftatτ, zero at both neighbors.