financialkit.fixed_income#
Bond cash flows, prices and yield to maturity (scipy.optimize.brentq),
accrued interest and STRIPS; Macaulay, modified and Fisher-Weil duration,
convexity and key rate durations; Lutz forward rates; zero curves
bootstrapped from bond prices or par rates, Carleton-Cooper discount
factors and McCulloch’s spline discount function (numpy.linalg.lstsq);
Nelson-Siegel and Svensson fits (scipy.optimize.least_squares) and the
principal components of curve moves (numpy.linalg.eigh); Redington
immunization and the Fong-Vasicek \(M^2\) bound; and interest-rate and
currency swaps.
financialkit.fixed_income: bonds, duration, yield curves, immunization and swaps.
Bond cash flows, prices and yield to maturity (scipy.optimize.brentq()),
accrued interest and STRIPS; Macaulay, modified and Fisher-Weil duration,
convexity and Ho’s key rate durations; Lutz forward rates, bootstrapping
a zero curve from bond prices or par rates (hand-written, solving each
node with brentq()), Carleton-Cooper discount
factors and McCulloch’s spline discount function
(numpy.linalg.lstsq()), Nelson-Siegel and Svensson fits
(scipy.optimize.least_squares()) and the principal components of
curve moves (numpy.linalg.eigh()); Redington immunization and the
Fong-Vasicek \(M^2\) bound; and interest-rate and currency swaps
valued as pairs of bonds.
Every curve is a YieldCurve of continuously compounded zero
rates, and every function that takes a curve also accepts a flat rate.
Times are year fractions; converting dates under a day-count convention
belongs to financialkit.time_value.
- class financialkit.fixed_income.BondRisk(price, macaulay_duration, modified_duration, convexity, yield_=0.0, compounding=None)[source]#
Bases:
objectPrice and first/second-order yield sensitivities of a cash-flow stream.
- Parameters:
- compounding: object = None#
Compounding frequency of the yield (
None= continuous).- Type:
int or None
- modified_duration: float#
\(-\frac{1}{P}\frac{dP}{dy}\); equals Macaulay under continuous compounding.
- Type:
- class financialkit.fixed_income.CashFlows(times, amounts)[source]#
Bases:
objectA stream of fixed amounts paid at times (years from today).
- present_value(curve)[source]#
Sum of the amounts discounted on curve.
- Return type:
- Parameters:
curve (YieldCurve | float)
- class financialkit.fixed_income.CurveFitResult(curve, params, residuals, model='')[source]#
Bases:
objectA fitted term structure and its goodness of fit.
- Parameters:
curve (YieldCurve)
params (ndarray)
residuals (ndarray)
model (str)
- curve: YieldCurve#
The fitted curve.
- Type:
- class financialkit.fixed_income.CurvePCAResult(tenors, eigenvalues, loadings, scores)[source]#
Bases:
objectPrincipal components of yield-curve changes (Litterman-Scheinkman).
- property explained_variance_ratio: ndarray#
Share of total variance carried by each component.
- Type:
ndarray
- class financialkit.fixed_income.FlatCurve(rate)[source]#
Bases:
YieldCurveThe same continuously compounded rate at every maturity.
- Parameters:
rate (float)
- class financialkit.fixed_income.ImmunizationResult(holdings, assets, liabilities, curve, pv_assets=0.0, pv_liabilities=0.0, duration_assets=0.0, duration_liabilities=0.0, convexity_assets=0.0, convexity_liabilities=0.0, extra=<factory>)[source]#
Bases:
objectAsset holdings that immunize a liability stream against parallel shifts (Redington).
- Parameters:
- curve: YieldCurve#
The curve the match was made on.
- Type:
- class financialkit.fixed_income.KeyRateDurationResult(key_tenors, durations, parallel_duration, price)[source]#
Bases:
objectHo’s key rate durations of a cash-flow stream.
- class financialkit.fixed_income.NelsonSiegelCurve(beta0, beta1, beta2, tau)[source]#
Bases:
YieldCurveNelson and Siegel’s (1987) parsimonious yield curve.
\[r(t) = \beta_0 + \beta_1 \frac{1 - e^{-t/\tau}}{t/\tau} + \beta_2 \left(\frac{1 - e^{-t/\tau}}{t/\tau} - e^{-t/\tau}\right)\]\(\beta_0\) is the long rate, \(\beta_0 + \beta_1\) the short rate, \(\beta_2\) a hump whose position \(\tau\) sets.
Examples
>>> curve = NelsonSiegelCurve(0.05, -0.02, 0.01, 2.0) >>> round(float(curve.zero_rate(0.0)), 6), round(float(curve.zero_rate(1e6)), 6) (0.03, 0.05)
- class financialkit.fixed_income.ShiftedCurve(base, delta)[source]#
Bases:
YieldCurveA base curve plus a spread on its zero rates: a parallel shift (float) or a shaped shock (callable).
- Parameters:
base (YieldCurve)
delta (float | Callable[[np.ndarray], np.ndarray])
- class financialkit.fixed_income.SplineDiscountCurve(coefficients, knots, degree=3)[source]#
Bases:
YieldCurveMcCulloch’s spline discount function \(d(t) = 1 + \sum_j a_j g_j(t)\).
The basis is the truncated power basis of the given degree, \(t, t^2, \dots, t^p\) and \((t - \kappa)_+^p\) at each interior knot \(\kappa\), so \(d(0) = 1\) holds by construction.
- Parameters:
coefficients (array_like) – The \(a_j\), polynomial terms first.
knots (array_like) – Interior knots (years).
degree (
int) – 2 (McCulloch, 1971) or 3 (McCulloch, 1975).
- class financialkit.fixed_income.SvenssonCurve(beta0, beta1, beta2, beta3, tau1, tau2)[source]#
Bases:
YieldCurveSvensson’s (1994) extension of Nelson-Siegel with a second hump.
\[r(t) = \text{NS}(t; \beta_0, \beta_1, \beta_2, \tau_1) + \beta_3 \left(\frac{1 - e^{-t/\tau_2}}{t/\tau_2} - e^{-t/\tau_2}\right)\]
- class financialkit.fixed_income.SwapValuation(value, receive_leg, pay_leg, kind='')[source]#
Bases:
objectValue of a swap as the difference of two bonds: the leg received less the leg paid.
- class financialkit.fixed_income.YieldCurve[source]#
Bases:
ABCA term structure of continuously compounded zero rates.
Subclasses implement
zero_rate(); discount factors, forward rates and par yields are derived here, so every curve answers them the same way.- forward_rate(t1, t2)[source]#
Continuously compounded forward rate between t1 and t2 (Lutz, 1940).
\[f(t_1, t_2) = \frac{r(t_2) t_2 - r(t_1) t_1}{t_2 - t_1}\]
- instantaneous_forward(t, h=1e-05)[source]#
Instantaneous forward rate \(f(t) = \partial_t [r(t) t]\), by central differences.
- Parameters:
h (float)
- par_yield(maturity, freq=2)[source]#
Coupon rate that prices a bond (or the fixed leg of a swap) at par.
\[c = m \, \frac{1 - P(0, T)}{\sum_i P(0, t_i)}\]with coupons at
T, T - 1/m, ...down to the first date after 0.- Parameters:
freq (int)
- class financialkit.fixed_income.ZeroCurve(tenors, rates)[source]#
Bases:
YieldCurveZero rates at node maturities, linearly interpolated and flat beyond the ends.
Linear interpolation in the zero rate is the convention of Hull’s bootstrapping examples (Options, Futures, and Other Derivatives, 9th ed., Sec. 4.5), and is what
bootstrap_zero_curve()solves under.- Parameters:
tenors (array_like, shape (n,)) – Strictly increasing positive maturities in years.
rates (array_like, shape (n,)) – Continuously compounded zero rates at tenors.
Examples
>>> curve = ZeroCurve([1.0, 2.0], [0.03, 0.04]) >>> round(float(curve.zero_rate(1.5)), 6) 0.035 >>> round(float(curve.forward_rate(1.0, 2.0)), 6) 0.05
- financialkit.fixed_income.accrued_interest(face, coupon_rate, maturity, freq=2)[source]#
Coupon interest earned since the last coupon date, owed by the buyer to the seller.
The accrual fraction is the elapsed share of the current coupon period measured in years; how calendar dates become those years is a day-count question for
financialkit.time_value; for accrual between calendar dates usefinancialkit.time_value.accrued_interest().Examples
>>> accrued_interest(100.0, 0.08, 1.375, freq=2) # a quarter of the way into a 4.0 coupon 1.0
- financialkit.fixed_income.as_curve(curve)[source]#
Return curve itself, or a
FlatCurveif it is a number.- Return type:
- Parameters:
curve (YieldCurve | float)
- financialkit.fixed_income.bond_cash_flows(face, coupon_rate, maturity, freq=2)[source]#
Cash flows of a fixed-coupon bullet bond.
- Parameters:
- Return type:
- Returns:
CashFlows
Examples
>>> cf = bond_cash_flows(100.0, 0.06, 1.5) >>> cf.times.tolist(), cf.amounts.tolist() ([0.5, 1.0, 1.5], [3.0, 3.0, 103.0])
- financialkit.fixed_income.bond_price(face, coupon_rate, maturity, y, freq=2, compounding=None)[source]#
Dirty price of a fixed-coupon bond at yield y; broadcasts over y.
Hull’s convention (continuous compounding,
compounding=None) is the default; passcompounding=freqfor the street convention of a yield compounded at the coupon frequency.Examples
Hull (9th ed., Sec. 4.4): a 2-year 6% semiannual bond yielding 6.76% continuously compounded is worth 98.39.
>>> round(bond_price(100.0, 0.06, 2.0, 0.0676), 2) 98.39 >>> round(bond_price(100.0, 0.05, 10.0, 0.05, compounding=2), 10) # at par 100.0
- financialkit.fixed_income.bond_risk(cash_flows, y, compounding=None)[source]#
Price, Macaulay and modified duration, and convexity at a single yield.
- Parameters:
- Return type:
- Returns:
BondRisk
Examples
Hull (9th ed., Table 4.6 and Sec. 4.9): a 3-year 10% semiannual bond at a 12% continuously compounded yield.
>>> from financialkit.fixed_income.systems.bonds import bond_cash_flows >>> risk = bond_risk(bond_cash_flows(100.0, 0.10, 3.0), 0.12) >>> round(risk.price, 3), round(risk.macaulay_duration, 3), round(risk.convexity, 2) (94.213, 2.653, 7.57)
- financialkit.fixed_income.bootstrap_par_curve(maturities, par_rates, freq=2)[source]#
Zero curve from par yields (or swap rates): every bond prices at par.
Examples
A flat 5% semiannual par curve is a flat zero curve at \(2 \ln 1.025\):
>>> curve = bootstrap_par_curve([1, 2, 5, 10], [0.05] * 4) >>> bool(abs(curve.rates - 2 * np.log(1.025)).max() < 1e-9) True
- financialkit.fixed_income.bootstrap_zero_curve(maturities, coupon_rates, prices, freq=2, face=100.0)[source]#
Strip a zero curve from coupon bond prices, shortest maturity first.
Each bond adds one node at its maturity. Its earlier cash flows are discounted on the curve built so far, linearly interpolated in the zero rate; when a coupon falls between the last known node and the new one, its rate depends on the unknown node too, so the node is solved for with
scipy.optimize.brentq()rather than by back-substitution.- Parameters:
- Return type:
- Returns:
ZeroCurve – Nodes at the bond maturities.
Examples
Hull (9th ed., Tables 4.3-4.4):
>>> curve = bootstrap_zero_curve([0.25, 0.5, 1.0, 1.5, 2.0], [0, 0, 0, 0.08, 0.12], [97.5, 94.9, 90.0, 96.0, 101.6]) >>> (100 * curve.rates).round(3).tolist() [10.127, 10.469, 10.536, 10.681, 10.808]
- financialkit.fixed_income.carleton_cooper_discount_factors(times, cash_flow_matrix, prices)[source]#
Discount factors by regressing bond prices on their cash flows.
Bond \(i\) pays \(C_{ik}\) at the common dates \(t_k\), so \(P_i = \sum_k C_{ik} d_k\). With as many bonds as dates this is a triangular system (bootstrapping); with more bonds it is solved in the least-squares sense, which averages out pricing noise.
- Parameters:
- Return type:
- Returns:
CurveFitResult –
paramsare the discount factors;curvethe implied zero curve.
Examples
>>> res = carleton_cooper_discount_factors([1.0, 2.0], [[105.0, 0.0], [6.0, 106.0]], [100.0, 99.0]) >>> res.params.round(6).tolist() [0.952381, 0.880054]
- financialkit.fixed_income.clean_price(face, coupon_rate, maturity, y, freq=2, compounding=None)[source]#
Quoted (clean) price: the dirty price less accrued interest; broadcasts over y.
The dirty price jumps down by a coupon on each payment date and climbs between them; the clean price removes that sawtooth.
- financialkit.fixed_income.convexity(cash_flows, y, compounding=None)[source]#
Convexity \(\frac{1}{P}\frac{d^2P}{dy^2}\) at yield y.
- financialkit.fixed_income.coupon_times(maturity, freq)[source]#
Coupon dates
maturity - k / freq > 0, ascending; the first period may be short.Examples
>>> coupon_times(1.25, 2).tolist() [0.25, 0.75, 1.25]
- financialkit.fixed_income.currency_swap_value(domestic_curve, foreign_curve, spot, domestic_notional, foreign_notional, domestic_rate, foreign_rate, maturity, freq=1, receive_foreign=True)[source]#
Value, in domestic currency, of a fixed-for-fixed currency swap with exchange of principal.
Each leg is a bond in its own currency discounted on its own curve; the foreign bond is converted at the spot rate.
- Parameters:
domestic_curve (
YieldCurve|float)foreign_curve (
YieldCurve|float)spot (
float) – Domestic currency per unit of foreign currency.domestic_notional (
float)foreign_notional (
float)domestic_rate (
float) – Annual fixed coupon rates on each leg.foreign_rate (
float) – Annual fixed coupon rates on each leg.maturity (
float)freq (
int)receive_foreign (
bool) – Value to the party receiving the foreign leg (True) or paying it.
- Return type:
- Returns:
SwapValuation
Examples
Hull (9th ed., Example 7.3): receive 5% on 1,200 million yen and pay 8% on $10 million annually for 3 years, with flat rates of 4% (yen) and 9% (dollar) and 110 yen per dollar.
>>> v = currency_swap_value(0.09, 0.04, 1 / 110, 10.0, 1200.0, 0.08, 0.05, 3.0) >>> round(v.value, 3) 1.543
- financialkit.fixed_income.effective_duration(cash_flows, curve, bump=0.0001)[source]#
Duration by repricing under parallel curve shifts of
+/- bump(central difference).Works for any curve and agrees with
fisher_weil_duration()to \(O(\text{bump}^2)\).- Return type:
- Parameters:
cash_flows (CashFlows)
curve (YieldCurve | float)
bump (float)
- financialkit.fixed_income.fisher_weil_convexity(cash_flows, curve)[source]#
Convexity under parallel shifts of a zero curve, \(\frac{1}{P}\sum_i t_i^2 c_i P(0, t_i)\).
- Return type:
- Parameters:
cash_flows (CashFlows)
curve (YieldCurve | float)
- financialkit.fixed_income.fisher_weil_duration(cash_flows, curve)[source]#
Fisher-Weil duration: Macaulay’s weights computed on a zero curve.
\[D_{FW} = \frac{1}{P} \sum_i t_i \, c_i \, P(0, t_i), \qquad P = \sum_i c_i \, P(0, t_i)\]It is exactly \(-\frac{1}{P}\frac{dP}{ds}\) for a parallel shift \(s\) of the continuously compounded zero curve.
Examples
>>> from financialkit.fixed_income.core.base import ZeroCurve >>> from financialkit.fixed_income.systems.bonds import bond_cash_flows >>> cf = bond_cash_flows(100.0, 0.05, 3.0, freq=1) >>> round(fisher_weil_duration(cf, ZeroCurve([1, 2, 3], [0.02, 0.03, 0.04])), 4) 2.8588
- Return type:
- Parameters:
cash_flows (CashFlows)
curve (YieldCurve | float)
- financialkit.fixed_income.fit_mcculloch_spline(bonds, prices, knots=None, degree=3)[source]#
Fit McCulloch’s spline discount function to bond prices by linear least squares.
Because the discount function is linear in its coefficients, so is every bond price: \(P_i - \sum_k c_{ik} = \sum_j a_j \sum_k c_{ik} g_j(t_{ik})\).
- Parameters:
- Return type:
- Returns:
CurveFitResult –
curveis aSplineDiscountCurve; residuals are in price units.
Examples
>>> from financialkit.fixed_income.core.base import FlatCurve >>> bonds = [bond_cash_flows(100.0, 0.05, T) for T in np.arange(1, 11)] >>> prices = [b.present_value(FlatCurve(0.04)) for b in bonds] >>> res = fit_mcculloch_spline(bonds, prices) >>> bool(abs(res.curve.zero_rate(5.0) - 0.04) < 1e-4) True
- financialkit.fixed_income.fit_nelson_siegel(tenors, yields, tau_grid=None)[source]#
Fit a
NelsonSiegelCurveto zero yields.For fixed \(\tau\) the betas are a linear least-squares problem, so the best \(\tau\) on a grid gives the starting point and
scipy.optimize.least_squares()then refines all four parameters.- Parameters:
tenors (array_like, shape (n,)) – Maturities and continuously compounded zero yields.
yields (array_like, shape (n,)) – Maturities and continuously compounded zero yields.
tau_grid (array_like, optional) – Candidate decay parameters; default 60 points from 0.1 to 15 years.
- Return type:
- Returns:
CurveFitResult
Examples
>>> t = np.array([0.25, 0.5, 1, 2, 3, 5, 7, 10, 20, 30]) >>> res = fit_nelson_siegel(t, NelsonSiegelCurve(0.05, -0.02, 0.015, 1.8).zero_rate(t)) >>> res.params.round(6).tolist() [0.05, -0.02, 0.015, 1.8]
- financialkit.fixed_income.fit_svensson(tenors, yields, tau_grid=None)[source]#
Fit a
SvenssonCurveto zero yields.The same two-stage scheme as
fit_nelson_siegel(), with a grid over pairs \(\tau_1 < \tau_2\) (which also keeps the two hump terms identifiable).- Return type:
Examples
>>> t = np.array([0.25, 0.5, 1, 2, 3, 4, 5, 7, 10, 15, 20, 30]) >>> true = SvenssonCurve(0.045, -0.015, -0.02, 0.03, 1.0, 6.0) >>> bool(fit_svensson(t, true.zero_rate(t)).rmse < 1e-7) True
- financialkit.fixed_income.fong_vasicek_bound(m2, K)[source]#
Fong-Vasicek lower bound \(-\tfrac12 K M^2\) on the relative change in horizon value.
Holds for a portfolio whose Fisher-Weil duration equals the horizon when the shock to instantaneous forward rates has slope at most K.
- financialkit.fixed_income.forward_rates(tenors, zero_rates)[source]#
Lutz’s forward rates for each period between consecutive tenors.
\[f_{k} = \frac{r_k t_k - r_{k-1} t_{k-1}}{t_k - t_{k-1}}, \qquad t_0 = 0\]The first period’s forward is the first zero rate. Under the expectations hypothesis these are the market’s forecasts of future short rates, and the zero rate is their time average.
- Parameters:
tenors (array_like, shape (n,)) – Strictly increasing positive maturities.
zero_rates (array_like, shape (n,)) – Continuously compounded zero rates.
- Return type:
- Returns:
ndarray, shape (n,)
Examples
Hull (9th ed., Table 4.5):
>>> forward_rates([1, 2, 3, 4, 5], [0.03, 0.04, 0.046, 0.05, 0.053]).round(4).tolist() [0.03, 0.05, 0.058, 0.062, 0.065]
- financialkit.fixed_income.horizon_value(cash_flows, curve, horizon)[source]#
Value of cash_flows carried forward to horizon: present value divided by \(P(0, H)\).
- Return type:
- Parameters:
cash_flows (CashFlows)
curve (YieldCurve | float)
horizon (float)
- financialkit.fixed_income.interest_rate_swap_value(curve, notional, fixed_rate, maturity, freq=2, pay_fixed=True, floating_rate=None)[source]#
Value of a fixed-for-floating interest-rate swap.
The fixed leg is a bond with coupon fixed_rate. The floating leg is worth par immediately after a reset, so today it is worth the notional plus the already-fixed next coupon, discounted from the next payment date. Payments on both legs fall on
maturity - k/freq.- Parameters:
curve (
YieldCurve|float) – Discount curve.notional (
float)fixed_rate (
float) – Annual fixed rate.maturity (
float) – Remaining life (years).freq (
int) – Payments per year on both legs.pay_fixed (
bool) – Value to the fixed-rate payer (True) or receiver.floating_rate (
float|None) – Annual rate already fixed for the next floating payment. May be omitted only on a reset date (maturity a whole number of periods), where the floating leg is worth exactly the notional.
- Return type:
- Returns:
SwapValuation
Examples
Hull (9th ed., Example 7.1): receive 8% and pay LIBOR on $100 million, 1.25 years left, LIBOR zero rates 10%, 10.5% and 11% at 3, 9 and 15 months, and 10.2% fixed for the next payment.
>>> from financialkit.fixed_income.core.base import ZeroCurve >>> curve = ZeroCurve([0.25, 0.75, 1.25], [0.10, 0.105, 0.11]) >>> v = interest_rate_swap_value(curve, 100.0, 0.08, 1.25, pay_fixed=False, floating_rate=0.102) >>> round(v.receive_leg, 3), round(v.pay_leg, 3), round(v.value, 3) (98.238, 102.505, -4.267)
- financialkit.fixed_income.key_rate_durations(cash_flows, curve, key_tenors, bump=0.0001)[source]#
Ho’s key rate durations: sensitivity to each key maturity’s zero rate.
Each key rate is shifted by
+/- bumpwith the triangular shape ofkey_rate_weights(), and the bond is repriced on the shocked curve.- Parameters:
cash_flows (
CashFlows)curve (
YieldCurve|float)key_tenors (array_like) – Key maturities (years), strictly increasing.
bump (
float) – Size of each shock in rate units.
- Return type:
- Returns:
KeyRateDurationResult
Examples
>>> from financialkit.fixed_income.systems.bonds import bond_cash_flows >>> res = key_rate_durations(bond_cash_flows(100.0, 0.05, 10.0), 0.04, [2.0, 5.0, 10.0, 30.0]) >>> bool(abs(res.durations.sum() - res.parallel_duration) < 1e-6) True
- financialkit.fixed_income.key_rate_weights(t, key_tenors)[source]#
Ho’s triangular key-rate shock shapes evaluated at maturities t.
Shock
kis 1 atkey_tenors[k], falls linearly to 0 at the neighbouring keys and is flat beyond the first and last keys. The shapes sum to 1 at every maturity, so the key rate durations add up to the parallel duration.- Parameters:
t (array_like, shape (n,))
key_tenors (array_like, shape (k,)) – Strictly increasing key maturities.
- Return type:
- Returns:
ndarray, shape (k, n)
Examples
>>> key_rate_weights([1.0, 3.5, 20.0], [2.0, 5.0, 10.0]).round(3).tolist() [[1.0, 0.5, 0.0], [0.0, 0.5, 0.0], [0.0, 0.0, 1.0]]
- financialkit.fixed_income.linear_forward_shock(level, slope, horizon)[source]#
Zero-rate shift produced by the forward-rate shock \(\Delta f(s) = a + b (s - H)\).
Integrating gives \(\Delta r(t) = a + b (t/2 - H)\); pass the result to
shifted().Examples
>>> from financialkit.fixed_income.core.base import CashFlows, FlatCurve >>> cf = CashFlows([2.0, 8.0], [50.0, 50.0]) >>> H = 5.0 # equal weights on 2 and 8 at a zero rate: duration exactly 5 >>> base, shocked = FlatCurve(0.0), FlatCurve(0.0).shifted(linear_forward_shock(0.0, 0.002, H)) >>> change = horizon_value(cf, shocked, H) / horizon_value(cf, base, H) - 1 >>> bool(change >= fong_vasicek_bound(m_squared(cf, H, base), 0.002)) True
- financialkit.fixed_income.m_squared(cash_flows, horizon, curve)[source]#
Fong and Vasicek’s dispersion \(M^2 = \sum_i w_i (t_i - H)^2\), with present-value weights \(w_i\).
- Return type:
- Parameters:
cash_flows (CashFlows)
horizon (float)
curve (YieldCurve | float)
Examples
>>> m_squared(CashFlows([5.0], [1.0]), 5.0, 0.04) # a zero maturing at the horizon 0.0
- financialkit.fixed_income.macaulay_duration(cash_flows, y, compounding=None)[source]#
Macaulay duration \(D = \sum_i t_i \, c_i \, d(t_i) / P\) at yield y.
Examples
>>> from financialkit.fixed_income.systems.bonds import bond_cash_flows >>> macaulay_duration(bond_cash_flows(100.0, 0.0, 7.0), 0.05) # a zero's duration is its maturity 7.0
- financialkit.fixed_income.modified_duration(cash_flows, y, compounding=None)[source]#
Modified duration \(-\frac{1}{P}\frac{dP}{dy}\): Hicks’s elasticity in yield units.
Equals \(D_{mac} / (1 + y/m)\) for a yield compounded m times a year, and \(D_{mac}\) itself under continuous compounding.
Examples
>>> from financialkit.fixed_income.systems.bonds import bond_cash_flows >>> cf = bond_cash_flows(100.0, 0.06, 5.0) >>> d_mac = macaulay_duration(cf, 0.06, compounding=2) >>> bool(abs(modified_duration(cf, 0.06, compounding=2) - d_mac / 1.03) < 1e-12) True
- financialkit.fixed_income.plot_cash_flows(cash_flows, curve=None, ax=None)[source]#
Bar chart of a cash-flow stream, overlaid with present values if a curve is given.
- Returns:
matplotlib.axes.Axes
- Parameters:
cash_flows (CashFlows)
curve (YieldCurve | float | None)
- financialkit.fixed_income.plot_key_rate_durations(result, ax=None, label=None)[source]#
Bar chart of key rate durations against key maturity.
- Returns:
matplotlib.axes.Axes
- Parameters:
result (KeyRateDurationResult)
label (str | None)
- financialkit.fixed_income.plot_pca_loadings(result, n_components=3, ax=None)[source]#
Loadings of the leading principal components against maturity.
- Returns:
matplotlib.axes.Axes
- Parameters:
result (CurvePCAResult)
n_components (int)
- financialkit.fixed_income.plot_price_yield(cash_flows, y0, compounding=None, dy=0.04, ax=None)[source]#
Price-yield curve with its duration tangent and duration-plus-convexity parabola at y0.
- financialkit.fixed_income.plot_surplus(result, shifts=None, ax=None)[source]#
Surplus (assets minus liabilities) against a parallel shift of the curve, in basis points.
- Returns:
matplotlib.axes.Axes
- Parameters:
result (ImmunizationResult)
- financialkit.fixed_income.plot_yield_curve(curve, t_max=30.0, ax=None, label='', show_forward=True)[source]#
Zero curve, and optionally its instantaneous forward curve, in percent.
- Parameters:
curve (
YieldCurve)t_max (
float) – Longest maturity plotted.ax (matplotlib.axes.Axes, optional) – Axes to draw on; a new figure is created if omitted.
label (
str) – Legend label prefix.show_forward (
bool) – Also draw the instantaneous forward curve (dashed).
- Returns:
matplotlib.axes.Axes
- financialkit.fixed_income.price_from_yield(cash_flows, y, compounding=None)[source]#
Present value of cash_flows discounted at a single yield y; broadcasts over y.
- financialkit.fixed_income.redington_immunize(liabilities, assets, curve)[source]#
Hold two assets in the amounts that match the liabilities’ present value and duration.
Solves the 2x2 linear system \(\sum_j h_j PV_j = PV_L\) and \(\sum_j h_j PV_j D_j = PV_L D_L\) (Fisher-Weil durations on curve). Check
redington_satisfiedfor Redington’s third condition, asset convexity above liability convexity; it holds when the assets’ maturities straddle the liabilities’.- Parameters:
liabilities (
CashFlows)assets (
list) – Cash flows per unit of each asset.curve (
YieldCurve|float)
- Return type:
- Returns:
ImmunizationResult
Examples
>>> from financialkit.fixed_income.systems.bonds import bond_cash_flows >>> L = CashFlows([10.0], [1000.0]) >>> res = redington_immunize(L, [bond_cash_flows(1.0, 0.0, 5.0), bond_cash_flows(1.0, 0.0, 20.0)], 0.05) >>> res.holdings.round(2).tolist(), res.redington_satisfied ([519.2, 549.57], True)
- financialkit.fixed_income.simulate_yield_changes(tenors, n_obs, factor_vols=(0.006, 0.003, 0.0015), noise_vol=0.0004, tau=2.0, rng=None)[source]#
Daily zero-yield changes driven by level, slope and curvature shocks.
Each day’s change is \(\sum_k \sigma_k z_k \ell_k(t) + \varepsilon(t)\) with Nelson-Siegel loadings \(\ell_k\) (flat, decaying, humped) and independent maturity-specific noise, which is the factor structure Litterman and Scheinkman found in Treasury returns.
- Parameters:
tenors (array_like, shape (n,)) – Maturities (years).
n_obs (
int) – Number of days.factor_vols (tuple of float) – Daily standard deviations of the level, slope and curvature shocks.
noise_vol (
float) – Daily standard deviation of the idiosyncratic noise.tau (
float) – Nelson-Siegel decay parameter of the loadings.
- Return type:
- Returns:
ndarray, shape (n_obs, n)
- financialkit.fixed_income.strips_curve(times, strip_prices, face=100.0)[source]#
Zero curve implied by STRIPS prices: \(r(t) = -\ln(p/F)/t\).
Examples
>>> curve = strips_curve([0.5, 1.0], [97.5, 95.0]) >>> round(float(curve.zero_rate(1.0)), 6) 0.051293
- financialkit.fixed_income.strips_decomposition(cash_flows)[source]#
Split a coupon bond into the zero-coupon bonds (STRIPS) it is a portfolio of.
- Return type:
- Returns:
list of CashFlows – One single-payment stream per payment date, in date order.
- Parameters:
cash_flows (CashFlows)
Examples
>>> [s.amounts.tolist() for s in strips_decomposition(bond_cash_flows(100.0, 0.04, 1.0))] [[2.0], [102.0]]
- financialkit.fixed_income.strips_value(cash_flows, strip_prices, face=100.0)[source]#
Cost of replicating cash_flows with STRIPS quoted at strip_prices per face of each payment date.
Comparing this with the bond’s market price is the stripping / reconstitution trade: if the strips are worth more, buy the bond and strip it; if less, buy the strips and reconstitute.
- Parameters:
cash_flows (
CashFlows)strip_prices (array_like or YieldCurve) – Prices per face of zero-coupon bonds maturing on each payment date, or a curve to price them on.
face (
float)
- Return type:
- Returns:
float
- financialkit.fixed_income.swap_annuity(curve, maturity, freq=2)[source]#
Present value of 1 per year paid
freqtimes a year to maturity: the fixed leg’s PV01 times 10,000.- Return type:
- Parameters:
curve (YieldCurve | float)
maturity (float)
freq (int)
- financialkit.fixed_income.swap_rate(curve, maturity, freq=2)[source]#
Par swap rate: the fixed rate that makes a new swap worth zero.
\[K = \frac{1 - P(0, T)}{\frac{1}{m}\sum_i P(0, t_i)}\]It equals the par yield of a bond with the same schedule.
Examples
>>> from financialkit.fixed_income.core.base import FlatCurve >>> round(swap_rate(FlatCurve(np.log(1.03)), 5.0, freq=1), 10) 0.03
- Parameters:
curve (YieldCurve | float)
freq (int)
- financialkit.fixed_income.yield_curve_pca(changes, tenors=None)[source]#
Principal components of yield-curve changes: Litterman and Scheinkman’s level, slope and curvature.
- Parameters:
- Return type:
- Returns:
CurvePCAResult
Examples
>>> from financialkit.fixed_income.utils.synthetic import simulate_yield_changes >>> tenors = np.array([0.5, 1, 2, 3, 5, 7, 10, 20, 30]) >>> pca = yield_curve_pca(simulate_yield_changes(tenors, 2000, rng=0), tenors) >>> bool(pca.explained_variance_ratio[:3].sum() > 0.95) True
- financialkit.fixed_income.yield_discount_derivatives(y, t, compounding=None)[source]#
First and second derivatives of
discount_factor()with respect to the yield y.Continuous: \(-t e^{-yt}\) and \(t^2 e^{-yt}\). Periodic: \(-t (1+y/m)^{-mt-1}\) and \(t (t + 1/m) (1+y/m)^{-mt-2}\).
- Returns:
d1, d2 (ndarray)
- Parameters:
compounding (int | None)
- financialkit.fixed_income.yield_to_maturity(price, cash_flows, compounding=None, tol=1e-10)[source]#
The single discount rate that equates cash_flows’ present value to price.
The price is strictly decreasing in the yield for positive cash flows, so the root is unique; it is bracketed by expanding an interval and found with
scipy.optimize.brentq().- Parameters:
- Return type:
- Returns:
float
Examples
>>> cf = bond_cash_flows(100.0, 0.06, 2.0) >>> round(yield_to_maturity(98.39, cf), 4) # Hull, 9th ed., Sec. 4.4 0.0676