Convexity Conventions
Convexity is the second derivative of value with respect to a rate shock, divided by the initial value. Annual-yield and continuous-zero shocks produce different convexities, even when they start from the same price. See Coleman (2011), pp. 3–4, for the role of compounding in rate sensitivities.
Yield-model inputs use additive continuously compounded zero-rate shifts. Plain scalar inputs use annual compounding; explicit Rate inputs use their specified compounding. The shift is layered on the curve without refitting it.
Since v6.0, curve convexity uses continuous-zero shocks; see the Version Upgrade Guide. Both convexity(curve, cfs, times) and convexity(valuation_function, curve) are ≈ the sum of the full key-rate convexity matrix. See Shock coordinates for the coordinate each input uses.
Why the analytic formula contains t²
For fixed cashflows $CF_i$ paid at fixed times $t_i$ in years, write their initial present values as $PV_i = CF_i D(0,t_i)$. A parallel continuous-zero shift $s$ changes the value to
\[V(s) = \sum_i PV_i e^{-s t_i}.\]
Differentiating twice gives
\[V''(0) = \sum_i t_i^2 PV_i, \qquad C = \frac{V''(0)}{V(0)} = \sum_i w_i t_i^2, \qquad w_i = \frac{PV_i}{V(0)}.\]
Each derivative contributes a factor $-t_i$. Payments at years 1, 2, and 3 therefore receive time weights 1, 4, and 9. The formula uses squared payment times, weighted by present value, and applies to nonflat curves too. Nawalkha, Soto, and Beliaeva (2005), Interest Rate Risk Modeling, chapter 1, pp. 5–6, equation (1.2), gives this continuous-rate definition.
The callback API derives convexity by differentiating the shocked valuation. The cashflow API evaluates the formula directly. For rate-dependent cashflows, use a callback or contract so the derivative includes changes in the payments.
Two-curve convexity blocks
For parallel continuous-zero shifts $u$ to the base curve and $v$ to the credit curve, convexity(valuation, base, credit) returns the blocks keyed by role, (; base = (; base, credit), credit = (; base, credit)), as the convexity field of sensitivities(valuation, SecondOrder(), base, credit) does. With nonzero initial value $V$, these are
\[C_{bb} = \frac{V_{uu}}{V}, \qquad C_{cc} = \frac{V_{vv}}{V}, \qquad C_{bc} = \frac{V_{uv}}{V}.\]
base.base is $C_{bb}$, credit.credit is $C_{cc}$, and base.credit and credit.base are both $C_{bc}$, with no extra factor of two. With KeyRates, each block is a matrix, and credit.base is the transpose of base.credit, which need not be symmetric. Including the first-order exposures, the change in value is approximately
\[\Delta V \approx -10^4(\mathrm{IR01}\,u + \mathrm{CS01}\,v) + \frac{V}{2}\left(C_{bb}u^2 + 2C_{bc}uv + C_{cc}v^2\right).\]
The shifts are in decimal rate units: one basis point is $10^{-4}$. The mixed term thus contributes $V C_{bc}uv$. Its two symmetric entries in the Hessian cancel the one-half in the quadratic expansion.
For fixed cashflows discounted at base + credit, value depends on $u+v$, so all four blocks equal the combined curve's convexity. A five-year zero-coupon payment has continuous-zero convexity $5^2 = 25$:
julia> using ActuaryUtilities, FinanceModels, FinanceCore
julia> base = Yield.Constant(Continuous(0.03)); credit = Yield.Constant(Continuous(0.02));
julia> blocks = convexity((b, c) -> pv(b + c, [100.0], [5.0]), base, credit);
julia> all(c -> c ≈ 25, (blocks.base.base, blocks.base.credit, blocks.credit.base, blocks.credit.credit))
true
julia> combined = convexity(base + credit, [100.0], [5.0]);
julia> blocks.base.base + 2 * blocks.base.credit + blocks.credit.credit ≈ 4 * combined
trueThe last line describes moving both curves by the same amount: the combined shift doubles, so its second-order contribution is four times that of shifting one curve alone. Equal blocks do not mean that the cross contribution can be omitted. Two curves: IR01 and CS01 gives a floater example where the curves have different roles and their convexities differ.
Empty and all-zero streams follow the Zero cashflow streams convention.
Dollar convexity
DollarConvexity selects the second derivative itself, $H = ∂^2V/∂s^2$ (a matrix $∂^2V/∂s_i∂s_j$ with KeyRates, blocks keyed by role for several curves), in the same shock coordinates. It has no factor of one-half and no basis-point scaling: its unit is currency per squared unit of decimal rate. Normalized convexity is $H/V$. Second-order sensitivities return it as dollar_convexity.
For a shift of $b$ basis points, the second-order change in value and the derivative per squared basis point are
estimated_pnl = -dv01 * b + 0.5 * dollar_convexity * (b / 10_000)^2
hessian_per_bp_squared = dollar_convexity * 1e-8Like DV01, dollar convexity is defined at zero value, keeps the position's sign, and adds across positions; normalized convexity needs a nonzero portfolio value. A position worth zero at a 0% continuous rate still has dollar risk:
julia> using ActuaryUtilities, FinanceCore
julia> cfs, times = [-1.0, 1.0], [0.0, 1.0];
julia> pv(Continuous(0.0), cfs, times), duration(DV01(), Continuous(0.0), cfs, times)
(0.0, 0.0001)
julia> convexity(DollarConvexity(), Continuous(0.0), cfs, times)
1.0Worked example: 11.26, 8.40, and annual-yield convexity
Consider cashflows [5, 5, 105] at years [1, 2, 3], discounted at a 4% annual yield. Their present value is 102.775091. The equivalent continuous zero rate is $\log(1.04) \approx 0.039220713$.
julia> using ActuaryUtilities, FinanceModels, FinanceCore
julia> cfs = [5.0, 5.0, 105.0]; times = [1.0, 2.0, 3.0];
julia> curve = Yield.Constant(Periodic(0.04, 1));
julia> valuation(c) = pv(c, cfs, times);
julia> kr = KeyRates(times);
julia> results = (convexity(curve, cfs, times), # analytic fast path
convexity(valuation, curve), # scalar AD
sum(convexity(kr, curve, cfs, times)),
sum(convexity(valuation, kr, curve)));
julia> round.(results; digits=6)
(8.400872, 8.400872, 8.400872, 8.400872)For this example, with $y=0.04$ and the same present-value weights:
| Quantity | Formula | Value |
|---|---|---|
| Continuous-zero convexity | $\sum_i w_i t_i^2$ | 8.400872 |
| Annual-yield convexity | $\sum_i w_i t_i(t_i+1)/(1+y)^2$ | 10.412662 |
| Former constant-curve statistic | $\sum_i w_i t_i(t_i+1)$ | 11.262335 |
The former statistic omits the $(1+y)^2$ divisor required for annual-yield convexity. Clarke, de Silva, and Thorley (2013), Fundamentals of Futures and Options, appendix p. 127, equation (A.15) and the following modification, distinguishes these annual-compounding quantities. For annual-yield convexity, pass a scalar yield or explicit annual rate:
julia> round.((convexity(0.04, cfs, times),
convexity(Periodic(0.04, 1), cfs, times)); digits=6)
(10.412662, 10.412662)
julia> weights = (cfs ./ 1.04 .^ times) ./ valuation(curve);
julia> round(sum(weights .* times .* (times .+ 1)); digits=6)
11.262335Summing key-rate convexities includes cross terms
The key-rate bumps interpolate linearly between knots, following the localized spot-rate approach of Ho (1992), “Key Rate Durations: Measures of Interest Rate Risks,” Journal of Fixed Income 2(2), pp. 29–44. The hats, including the flat endpoint extrapolations, sum to one. An equal shift to every key rate therefore produces the parallel shock.
For $K_{jk}=V^{-1}\partial^2 V/\partial s_j\partial s_k$, the chain rule gives
\[C_{\mathrm{parallel}} = \mathbf{1}^{\mathsf T} K \mathbf{1} = \sum_{j,k} K_{jk}.\]
Sum every matrix entry, including mixed derivatives. See Reitano (1991), “Multivariate Duration Analysis”, section 3(c), definitions 3.3–3.5 and equation (3.28) (SOA monograph reprint).
With knots at years 1 and 3, the year-2 cashflow responds to both bumps, producing nonzero off-diagonal entries:
julia> K = convexity(KeyRates([1.0, 3.0]), curve, cfs, times);
julia> (sum(K) ≈ convexity(valuation, curve),
K[1, 2] > 0,
K[1, 1] + K[2, 2] < sum(K))
(true, true, true)The identity holds for derivatives under the specified hat shocks. Finite-bump estimates have approximation error.