financialkit.credit#

Altman’s Z-score and Fisher’s linear discriminant; Merton’s structural model through financialkit.options.black_scholes_price(), its calibration to equity and the Crosbie-Bohn distance to default; Black-Cox first passage and Leland’s endogenous default; piecewise-constant hazard curves, Jarrow-Turnbull bonds and the Duffie-Singleton, treasury and face-value recovery conventions (with a CIR intensity through financialkit.interest_rate_models.CIRModel); O’Kane-Turnbull CDS legs, par spreads, upfronts and the ISDA-style hazard bootstrap; CreditMetrics migration and credit VaR, Jarrow-Lando-Turnbull generator matrices (scipy.linalg.expm and logm) and the Israel-Rosenthal-Wei regularization; Vasicek’s large homogeneous portfolio, CreditRisk+ by Panjer’s recursion, Li’s Gaussian copula (through financialkit.risk.gaussian_copula_sample()), the Basel II IRB formula and Hull-White tranche pricing.

financialkit.credit: credit scoring, structural and reduced-form default models, CDS, rating migration and portfolio credit risk.

Altman’s Z-score; Merton’s structural model, its calibration to equity and Crosbie-Bohn distance to default (through financialkit.options.black_scholes_price()); Black-Cox first passage and Leland’s endogenous default; Jarrow-Turnbull hazard curves and Duffie-Singleton recovery of market value; O’Kane-Turnbull CDS legs, par spreads, upfronts and the ISDA-style hazard bootstrap; CreditMetrics migration, Jarrow-Lando-Turnbull generators (scipy.linalg.expm()) and Israel-Rosenthal-Wei regularization; Vasicek’s large homogeneous portfolio, CreditRisk+ by Panjer’s recursion, Li’s Gaussian copula, the Basel II IRB formula and Hull-White tranche pricing. Time is in years, rates are continuously compounded and recovery is a fraction of face value.

class financialkit.credit.CDSResult(par_spread, protection_leg, rpv01, accrued_on_default, payment_times)[source]#

Bases: object

Legs of a credit default swap, per unit notional.

Parameters:
accrued_on_default: float#

Part of rpv01 paid as premium accrued at default.

mtm(spread)[source]#

Value to the protection buyer of a contract paying spread.

Return type:

float

Parameters:

spread (float)

par_spread: float#

Spread that sets the premium and protection legs equal.

payment_times: ndarray#

Premium payment dates.

Type:

ndarray

protection_leg: float#

Present value of the default payment (1 - R).

rpv01: float#

premium-leg value per unit of spread, accrued premium included.

Type:

Risky annuity

class financialkit.credit.CreditMetricsResult(values, probabilities)[source]#

Bases: object

A CreditMetrics value distribution over rating states.

Parameters:
credit_var(alpha=0.99)[source]#

Mean value minus the 1 - alpha percentile.

Return type:

float

Parameters:

alpha (float)

property mean: float#

Expected value.

Type:

float

percentile(p)[source]#

Smallest value whose cumulative probability reaches p (e.g. 0.01).

Return type:

float

Parameters:

p (float)

probabilities: ndarray#

Probability of each value.

Type:

ndarray

property std: float#

Standard deviation of the value.

Type:

float

values: ndarray#

Portfolio values, one per scenario or rating state.

Type:

ndarray

class financialkit.credit.HazardCurve(knots, hazards)[source]#

Bases: SurvivalCurve

Piecewise-constant default intensity.

The hazard is hazards[i] on (knots[i-1], knots[i]] (with knots[-1] = 0) and stays at the last value beyond the last knot.

Parameters:
  • knots (ArrayLike) – Increasing end times of the hazard segments (years, positive).

  • hazards (ArrayLike) – Non-negative intensities.

Examples

>>> c = HazardCurve([1.0, 3.0], [0.01, 0.03])
>>> round(c.survival(2.0), 6) == round(float(np.exp(-0.04)), 6)
True
>>> round(c.inverse_cumulative_hazard(0.04), 12)
2.0
cumulative_hazard(t)[source]#

\(\Lambda(t)\), linear between knots.

Return type:

ndarray | float

Parameters:

t (ArrayLike)

classmethod flat(hazard)[source]#

A constant intensity: exponential default time with mean 1 / hazard.

Return type:

HazardCurve

Parameters:

hazard (float)

hazard(t)[source]#

Intensity \(\lambda(t)\).

Return type:

ndarray | float

Parameters:

t (ArrayLike)

inverse_cumulative_hazard(x)[source]#

Time at which \(\Lambda\) reaches x (inf if it never does).

Return type:

ndarray | float

Parameters:

x (ArrayLike)

class financialkit.credit.KMVResult(asset_values, asset_volatility, drift, distance_to_default, expected_default_frequency, n_iter, volatility_history)[source]#

Bases: object

Crosbie and Bohn’s (2003) iterative estimate of the asset path and distance to default.

Parameters:
asset_values: ndarray#

Implied asset values, one per equity observation.

Type:

ndarray, shape (n,)

asset_volatility: float#

Annualised volatility of the implied asset log returns.

distance_to_default: float#

Physical distance to default at the last observation.

drift: float#

Annualised drift mu of the asset values.

expected_default_frequency: float#

\(N(-DD)\), the normal-model EDF (Moody’s KMV maps DD to EDF empirically).

n_iter: int#

Iterations until the asset volatility converged.

volatility_history: ndarray#

Asset volatility after each iteration, starting from the initial guess.

Type:

ndarray

class financialkit.credit.LelandResult(default_barrier, debt, equity, firm_value, leverage, credit_spread, default_probability_weight)[source]#

Bases: object

Leland’s (1994) perpetual-debt firm with endogenous default.

Parameters:
credit_spread: float#

coupon / debt - r.

debt: float#

Market value of the perpetual debt.

default_barrier: float#

Asset value V_B at which shareholders choose to default.

default_probability_weight: float#

\(p_B = (V/V_B)^{-2r/\sigma^2}\), the price of a claim paying 1 at default.

equity: float#

Market value of equity.

firm_value: float#

Unlevered assets plus tax shields minus bankruptcy costs.

leverage: float#

debt / firm_value.

class financialkit.credit.LossDistribution(losses, pmf, method)[source]#

Bases: object

A portfolio credit loss distribution on a grid of loss levels.

Parameters:
losses: ndarray#

Loss levels, increasing.

Type:

ndarray, shape (m,)

property mean: float#

Expected loss.

Type:

float

method: str#

How the distribution was obtained, e.g. "creditrisk+" or "monte_carlo".

pmf: ndarray#

Probability of each loss level.

Type:

ndarray, shape (m,)

quantile(alpha)[source]#

Loss quantile (credit VaR before subtracting the mean).

Return type:

float

Parameters:

alpha (float)

property std: float#

Standard deviation of the loss.

Type:

float

unexpected_loss(alpha=0.999)[source]#

Quantile minus expected loss, the economic-capital figure.

Return type:

float

Parameters:

alpha (float)

class financialkit.credit.MertonResult(equity, debt, asset_value, asset_volatility, equity_volatility, default_probability, distance_to_default, credit_spread, recovery_rate)[source]#

Bases: object

Merton’s (1974) firm: equity as a call on the assets, debt as the rest.

Parameters:
asset_value: float#

Firm asset value V.

asset_volatility: float#

Asset volatility sigma_V.

credit_spread: float#

Yield on the debt minus r, continuously compounded.

debt: float#

Market value of the zero-coupon debt, V - equity.

default_probability: float#

Risk-neutral probability of default at maturity, \(N(-d_2)\).

distance_to_default: float#

\(d_2\), the number of standard deviations between V and the default point.

equity: float#

Market value of equity, a Black-Scholes call on the assets struck at the face value.

equity_volatility: float#

Instantaneous equity volatility, \(N(d_1)\sigma_V V / E\).

recovery_rate: float#

Risk-neutral expected recovery as a fraction of face value, given default.

class financialkit.credit.SurvivalCurve[source]#

Bases: ABC

A default-time distribution given by its cumulative hazard \(\Lambda(t)\).

Survival is \(Q(t) = e^{-\Lambda(t)}\); Lando (1998) calls this the Cox-process view of default, with the intensity deterministic here.

abstractmethod cumulative_hazard(t)[source]#

Integrated hazard \(\Lambda(t)\), non-decreasing with \(\Lambda(0) = 0\).

Return type:

ndarray | float

Parameters:

t (ArrayLike)

default_density(t)[source]#

Density of the default time, \(\lambda(t) Q(t)\).

Return type:

ndarray | float

Parameters:

t (ArrayLike)

default_probability(t, s=None)[source]#

\(P(\tau \le t)\), or \(P(t < \tau \le s)\) when s is given.

Return type:

ndarray | float

Parameters:
  • t (ArrayLike)

  • s (ArrayLike | None)

abstractmethod hazard(t)[source]#

Instantaneous default intensity \(\lambda(t)\).

Return type:

ndarray | float

Parameters:

t (ArrayLike)

abstractmethod inverse_cumulative_hazard(x)[source]#

The time at which \(\Lambda\) reaches x.

Return type:

ndarray | float

Parameters:

x (ArrayLike)

sample_default_times(n, rng=None, uniforms=None)[source]#

Default times by inversion: \(\tau = \Lambda^{-1}(-\ln U)\).

Parameters:
  • n (int) – Number of draws (ignored when uniforms is given).

  • rng (Generator | int | None)

  • uniforms (ArrayLike | None) – Uniforms to invert, e.g. a copula sample; inf marks no default.

Return type:

ndarray

Returns:

ndarray

survival(t)[source]#

Survival probability \(Q(t) = P(\tau > t)\).

Return type:

ndarray | float

Parameters:

t (ArrayLike)

class financialkit.credit.TrancheResult(attachment, detachment, par_spread, expected_loss, times, protection_leg, rpv01)[source]#

Bases: object

A synthetic CDO tranche priced in the one-factor Gaussian copula.

Parameters:
attachment: float#

Lower loss point, a fraction of the portfolio notional.

detachment: float#

Upper loss point.

expected_loss: ndarray#

Expected tranche loss (fraction of tranche notional) at each payment time.

Type:

ndarray

par_spread: float#

Running spread that prices the tranche at par.

protection_leg: float#

Present value of tranche losses, per unit tranche notional.

rpv01: float#

Premium-leg value per unit spread, per unit tranche notional.

times: ndarray#

Payment times.

Type:

ndarray

financialkit.credit.altman_ratios(working_capital, retained_earnings, ebit, market_equity, sales, total_assets, total_liabilities)[source]#

Altman’s five ratios \(X_1, \dots, X_5\), stacked on the last axis.

Parameters:
  • working_capital (ArrayLike) – Balance-sheet and income-statement items, in the same currency unit.

  • retained_earnings (ArrayLike) – Balance-sheet and income-statement items, in the same currency unit.

  • ebit (ArrayLike) – Balance-sheet and income-statement items, in the same currency unit.

  • market_equity (ArrayLike) – Balance-sheet and income-statement items, in the same currency unit.

  • sales (ArrayLike) – Balance-sheet and income-statement items, in the same currency unit.

  • total_assets (ArrayLike) – Balance-sheet and income-statement items, in the same currency unit.

  • total_liabilities (ArrayLike) – Balance-sheet and income-statement items, in the same currency unit.

Return type:

ndarray

Returns:

ndarray, shape (…, 5)

financialkit.credit.altman_z_score(working_capital, retained_earnings, ebit, market_equity, sales, total_assets, total_liabilities)[source]#

Altman’s (1968) Z-score for public manufacturers.

Parameters:
Return type:

ndarray | float

Returns:

float or ndarray

Examples

A firm with total assets 670, working capital 170, retained earnings 300, EBIT 60, sales 2200, equity worth 380 and liabilities of 240:

>>> round(altman_z_score(170, 300, 60, 380, 2200, 670, 240), 2)
5.46
financialkit.credit.altman_zone(z, lower=1.81, upper=2.99)[source]#

Classify Z-scores into Altman’s distress, grey and safe zones.

Parameters:
  • z (ArrayLike)

  • lower (float) – Zone boundaries (Altman 1968).

  • upper (float) – Zone boundaries (Altman 1968).

Return type:

ndarray | str

Returns:

str or ndarray of str

Examples

>>> altman_zone([1.2, 2.5, 3.4]).tolist()
['distress', 'grey', 'safe']
financialkit.credit.basel_correlation(pd, asset_class='corporate', sales=None)[source]#

Supervisory asset correlation \(R\) for an asset class.

  • "corporate" (also sovereign and bank): 12% to 24%, interpolated with weight \((1 - e^{-50 PD})/(1 - e^{-50})\), less the SME adjustment \(0.04 (1 - (S - 5)/45)\) when annual sales \(S\) (EUR millions, clipped to 5-50) are given;

  • "mortgage": 15%; "revolving": 4%;

  • "retail" (other retail): 3% to 16% with weight factor 35.

Parameters:
Return type:

ndarray | float

Returns:

float or ndarray

financialkit.credit.basel_irb_capital(pd, lgd=0.45, maturity=2.5, asset_class='corporate', sales=None, alpha=0.999, pd_floor=0.0003)[source]#

IRB capital requirement \(K\) per unit of exposure at default.

Retail classes have no maturity adjustment. A defaulted exposure (pd = 1) gets zero unexpected-loss capital.

Parameters:
  • pd (ArrayLike) – One-year default probability (floored at 0.03%).

  • lgd (ArrayLike) – Loss given default (45% is the foundation-IRB senior unsecured value).

  • maturity (ArrayLike) – Effective maturity in years.

  • asset_class (str)

  • sales (ArrayLike | None) – Annual sales in EUR millions for the SME correlation adjustment.

  • alpha (float) – Confidence level, 99.9% in the framework.

  • pd_floor (float)

Return type:

ndarray | float

Returns:

float or ndarray

Examples

A corporate loan with PD 1%, LGD 45% and M = 2.5:

>>> round(12.5 * basel_irb_capital(0.01), 4)
0.9232
financialkit.credit.basel_maturity_adjustment(pd, maturity=2.5)[source]#

Maturity adjustment \((1 + (M - 2.5) b) / (1 - 1.5 b)\), with \(b = (0.11852 - 0.05478 \ln PD)^2\).

Parameters:
  • pd (ArrayLike)

  • maturity (ArrayLike) – Effective maturity \(M\) in years (1 to 5 in the framework).

Return type:

ndarray | float

Returns:

float or ndarray

financialkit.credit.basel_risk_weight(pd, lgd=0.45, maturity=2.5, asset_class='corporate', sales=None, scaling=1.0)[source]#

Risk weight \(12.5 \cdot K\) (times the 1.06 scaling factor if passed).

Parameters:
Return type:

ndarray | float

Returns:

float or ndarray

Examples

Basel II framework, illustrative IRB risk weights (corporate, LGD 45%, M = 2.5):

>>> [round(100 * basel_risk_weight(p), 2) for p in (0.0003, 0.01)]
[14.44, 92.32]
financialkit.credit.black_cox_default_probability(V, K, t, mu, sigma, gamma=0.0, T=None)[source]#

First-passage default probability, 1 - black_cox_survival(...).

Parameters:
Return type:

ndarray | float

Returns:

float or ndarray

financialkit.credit.black_cox_survival(V, K, t, mu, sigma, gamma=0.0, T=None)[source]#

Probability that assets stay above the Black-Cox barrier up to time t.

Parameters:
  • V (ArrayLike) – Current asset value (above the barrier).

  • K (ArrayLike) – Barrier level at T.

  • t (ArrayLike) – Horizon.

  • mu (ArrayLike) – Asset drift (r - payout under the pricing measure).

  • sigma (ArrayLike) – Asset volatility.

  • gamma (float) – Growth rate of the barrier; 0 gives a flat barrier K.

  • T (ArrayLike | None) – Date at which the barrier equals K; defaults to t.

Return type:

ndarray | float

Returns:

float or ndarray

Examples

With zero log-drift, the reflection principle doubles the probability of ending below the barrier:

>>> q = black_cox_survival(100.0, 70.0, 1.0, 0.02, 0.2)
>>> p_end = float(stats.norm.cdf(np.log(70 / 100) / 0.2))
>>> round(1 - q, 10) == round(2 * p_end, 10)
True
financialkit.credit.bootstrap_hazard_curve(maturities, spreads, r, recovery=0.4, frequency=4, steps_per_year=52)[source]#

Bootstrap a piecewise-constant hazard curve from CDS par spreads.

Solves the maturities in increasing order with scipy.optimize.brentq(), each new hazard segment chosen so the CDS to that maturity reprices at its quote while the earlier segments stay fixed, as in the ISDA standard model.

Parameters:
Return type:

HazardCurve

Returns:

HazardCurve

Examples

>>> c = bootstrap_hazard_curve([1, 3, 5], [0.006, 0.009, 0.012], 0.03)
>>> [round(1e4 * cds_par_spread(c, T, 0.03), 6) for T in (1, 3, 5)]
[60.0, 90.0, 120.0]
financialkit.credit.cds_legs(curve, maturity, r, recovery=0.4, frequency=4, steps_per_year=52)[source]#

Value both legs of a CDS per unit notional, starting today.

Parameters:
  • curve (SurvivalCurve) – Risk-neutral survival curve of the reference entity.

  • maturity (float) – Years to maturity.

  • r (float) – Flat continuously compounded risk-free rate.

  • recovery (float) – Recovery rate \(R\).

  • frequency (int) – Premium payments per year (4 for the standard quarterly schedule).

  • steps_per_year (int) – Grid for the default integrals; each premium period gets at least one step.

Return type:

CDSResult

Returns:

CDSResult

Examples

Hull’s (10th ed., Section 25.2) setting: hazard 2%, recovery 40%, rate 5%, annual payments, defaults half-way through each year.

>>> res = cds_legs(HazardCurve.flat(0.02), 5.0, 0.05, 0.4, frequency=1, steps_per_year=1)
>>> round(res.rpv01 - res.accrued_on_default, 4), round(res.protection_leg, 4)
(4.0728, 0.0506)
>>> round(1e4 * res.par_spread, 1)
123.0
financialkit.credit.cds_par_spread(curve, maturity, r, recovery=0.4, frequency=4, steps_per_year=52)[source]#

Par spread of a CDS, protection_leg / rpv01.

Parameters:
Return type:

float

Returns:

float

Examples

The credit triangle \(s = \lambda(1 - R)\) is the continuous-premium limit:

>>> round(1e4 * cds_par_spread(HazardCurve.flat(0.02), 5.0, 0.05, 0.4, frequency=365, steps_per_year=365), 1)
120.0
financialkit.credit.cds_upfront(curve, maturity, coupon, r, recovery=0.4, frequency=4, steps_per_year=52)[source]#

Upfront paid by the protection buyer of a fixed-coupon CDS, per unit notional.

protection_leg - coupon * rpv01; negative when the buyer is paid.

Parameters:
Return type:

float

Returns:

float

financialkit.credit.check_transition_matrix(P, atol=1e-08)[source]#

Validate a stochastic matrix: square, non-negative, rows summing to one.

Parameters:
Return type:

ndarray

Returns:

ndarray

financialkit.credit.conditional_default_probability(p, rho, M)[source]#

Default probability given the common factor, \(p(M)\).

Parameters:
  • p (ArrayLike) – Unconditional default probability.

  • rho (ArrayLike) – Asset correlation in [0, 1).

  • M (ArrayLike) – Factor value.

Return type:

ndarray | float

Returns:

float or ndarray

financialkit.credit.copula_default_times(curves, rho, n, n_names=None, df=None, rng=None)[source]#

Simulate correlated default times with a Gaussian (or t) copula.

Parameters:
Return type:

ndarray

Returns:

ndarray, shape (n, d) – Default times (inf for a name with zero hazard that never defaults).

financialkit.credit.creditmetrics_bbb_values()[source]#

One-year forward values of CreditMetrics’ five-year 6% BBB bond in each rating.

Return type:

ndarray

Returns:

ndarray, shape (8,) – Values per 100 face, ordered as RATINGS (default value is the mean senior-unsecured recovery, 51.13).

financialkit.credit.creditmetrics_bond(probabilities, values)[source]#

Value distribution of one bond over its possible ratings in a year.

Parameters:
  • probabilities (ArrayLike) – Migration probabilities (a row of the transition matrix).

  • values (ArrayLike) – Bond value in each end-of-year rating, default included.

Return type:

CreditMetricsResult

Returns:

CreditMetricsResult

Examples

CreditMetrics Technical Document, Chapter 3: the five-year BBB bond has mean value 107.09 and standard deviation 2.99.

>>> from financialkit.credit.utils.matrices import creditmetrics_bbb_values, creditmetrics_matrix
>>> res = creditmetrics_bond(creditmetrics_matrix(normalize=False)[3], creditmetrics_bbb_values())
>>> round(res.mean, 2), round(res.std, 2)
(107.09, 2.99)
financialkit.credit.creditmetrics_matrix(normalize=True)[source]#

The CreditMetrics one-year rating transition matrix.

Parameters:

normalize (bool) – Rescale rows to sum to one exactly (the published percentages are rounded, and two rows sum to 99.99% and 100.01%).

Return type:

ndarray

Returns:

ndarray, shape (8, 8) – Row i holds the probabilities of moving from RATINGS[i] to each rating.

Examples

>>> P = creditmetrics_matrix()
>>> round(float(P[3, 7]), 4)
0.0018
financialkit.credit.creditmetrics_portfolio(P, ratings, values, rho, n_sims, rng=None)[source]#

Monte Carlo CreditMetrics value distribution of a bond portfolio.

Parameters:
Return type:

CreditMetricsResult

Returns:

CreditMetricsResult – Equally weighted portfolio-value scenarios.

financialkit.credit.creditrisk_plus(exposures, pds, sigma=0.0, unit=1.0, n_max=None)[source]#

Single-sector CreditRisk+ loss distribution.

Parameters:
  • exposures (ArrayLike) – Loss given default of each obligor, in currency.

  • pds (ArrayLike) – Default probabilities (used as Poisson rates).

  • sigma (float) – Relative standard deviation of the sector default rate; 0 for independent (Poisson) defaults.

  • unit (float) – Loss unit; exposures are rounded to multiples of it (at least one unit).

  • n_max (int | None) – Largest loss, in units; defaults to the mean plus 15 standard deviations.

Return type:

LossDistribution

Returns:

LossDistribution – Losses in currency, on the grid unit * arange(n_max + 1).

Examples

The expected loss is the sum of rounded exposures times default probabilities:

>>> res = creditrisk_plus([100.0, 200.0, 300.0], [0.01, 0.02, 0.03], sigma=0.5, unit=100.0)
>>> round(res.mean, 4)
14.0
financialkit.credit.default_correlation(p1, p2, rho)[source]#

Correlation of the two default indicators implied by copula correlation rho.

Parameters:
Return type:

ndarray | float

Returns:

float or ndarray

financialkit.credit.distance_to_default(V, D, mu, sigma_V, T=1.0)[source]#

Crosbie and Bohn’s distance to default under the physical drift.

\[DD = \frac{\ln(V/D) + (\mu - \sigma_V^2/2) T}{\sigma_V\sqrt{T}}\]

With mu = r it is Merton’s \(d_2\).

Parameters:
  • V (ArrayLike) – Asset value, default point, asset drift, asset volatility and horizon.

  • D (ArrayLike) – Asset value, default point, asset drift, asset volatility and horizon.

  • mu (ArrayLike) – Asset value, default point, asset drift, asset volatility and horizon.

  • sigma_V (ArrayLike) – Asset value, default point, asset drift, asset volatility and horizon.

  • T (ArrayLike) – Asset value, default point, asset drift, asset volatility and horizon.

Return type:

ndarray | float

Returns:

float or ndarray

Examples

>>> round(distance_to_default(100.0, 60.0, 0.08, 0.25, 1.0), 4)
2.2383
financialkit.credit.duffie_singleton_cir_zero(T, r, lambda0, kappa, theta, sigma, recovery=0.4)[source]#

Recovery-of-market-value bond with a CIR default intensity and a flat riskless rate.

The loss-adjusted intensity \(L\lambda\) (with \(L = 1 - R\)) follows a CIR process with long-run mean \(L\theta\) and volatility \(\sigma\sqrt{L}\), so

\[P^d(0, T) = e^{-rT} A(T) e^{-B(T) L \lambda_0},\]

with \(A, B\) from financialkit.interest_rate_models.CIRModel.

Parameters:
  • T (ArrayLike) – Maturities.

  • r (float) – Flat risk-free rate.

  • lambda0 (float) – Initial intensity, mean-reversion speed, long-run mean and volatility of the intensity.

  • kappa (float) – Initial intensity, mean-reversion speed, long-run mean and volatility of the intensity.

  • theta (float) – Initial intensity, mean-reversion speed, long-run mean and volatility of the intensity.

  • sigma (float) – Initial intensity, mean-reversion speed, long-run mean and volatility of the intensity.

  • recovery (float) – Recovery as a fraction of market value.

Return type:

ndarray | float

Returns:

float or ndarray

Examples

With the intensity starting at its mean and almost no volatility, the bond is the constant-hazard one:

>>> a = duffie_singleton_cir_zero(5.0, 0.05, 0.02, 1.0, 0.02, 1e-3, 0.4)
>>> b = risky_zero_coupon(5.0, 0.05, 0.02, 0.4, "rmv")
>>> abs(a - b) < 1e-6
True
financialkit.credit.expected_tranche_loss(attachment, detachment, p, rho, recovery=0.4, n_names=None, n_nodes=96)[source]#

Expected tranche loss (fraction of tranche notional) at one horizon.

Parameters:
  • attachment (float)

  • detachment (float)

  • p (float) – Default probability of each name to the horizon.

  • rho (float) – Copula correlation.

  • recovery (float)

  • n_names (int | None) – Pool size; the large homogeneous pool limit if omitted.

  • n_nodes (int) – Gauss-Hermite nodes for a finite pool.

Return type:

float

Returns:

float

Examples

A [0, 100%] tranche loses the pool’s expected loss whatever the correlation:

>>> round(expected_tranche_loss(0.0, 1.0, 0.05, 0.3, 0.4), 10)
0.03
financialkit.credit.first_passage_crossing_probability(paths, barrier, sigma, dt)[source]#

Brownian-bridge probability that each discretely sampled path crossed the barrier.

Between two log-asset values above a log barrier \(b\) a Brownian bridge crosses with probability \(\exp(-2 (x_i - b)(x_{i+1} - b) / (\sigma^2 \Delta t))\), which removes the bias of only checking the barrier on the grid.

Parameters:
  • paths (ArrayLike) – Asset paths.

  • barrier (ArrayLike) – Barrier on the same grid (broadcast to paths).

  • sigma (float) – Asset volatility.

  • dt (float) – Time step.

Return type:

ndarray

Returns:

ndarray, shape (n_paths,) – Probability that each path hit the barrier.

financialkit.credit.generator_from_transition(P, t=1.0)[source]#

Candidate generator \(\log(P) / t\) (principal matrix logarithm).

The result has zero row sums but may have negative off-diagonal entries; see regularize_generator().

Parameters:
Return type:

ndarray

Returns:

ndarray, shape (k, k)

financialkit.credit.hazard_from_spread(spread, recovery=0.4)[source]#

Credit triangle: the hazard rate implied by a spread, \(\lambda = s / (1 - R)\).

Exact for a continuously paid CDS premium and a flat hazard and rate.

Parameters:
Return type:

ndarray | float

Returns:

float or ndarray

Examples

>>> round(hazard_from_spread(0.012, 0.4), 6)
0.02
financialkit.credit.is_valid_generator(Q, atol=1e-12)[source]#

Whether Q has zero row sums and non-negative off-diagonal entries.

Parameters:
Return type:

bool

Returns:

bool

financialkit.credit.jarrow_turnbull_zero(T, r, curve, recovery=0.0)[source]#

Defaultable zero-coupon bond under recovery of treasury.

\[P^d(0, T) = e^{-rT}\,[\delta + (1 - \delta) Q(T)]\]
Parameters:
  • T (ArrayLike) – Maturities.

  • r (ArrayLike) – Flat risk-free rate.

  • curve (SurvivalCurve) – Risk-neutral survival curve.

  • recovery (float) – Fraction \(\delta\) of the riskless bond paid at default.

Return type:

ndarray | float

Returns:

float or ndarray

Examples

>>> round(jarrow_turnbull_zero(5.0, 0.05, HazardCurve.flat(0.02), 0.4), 6)
0.734333
financialkit.credit.jlt_risky_zero(T, r, Q, rating, recovery=0.4, premia=None)[source]#

Jarrow-Lando-Turnbull zero-coupon bond of an issuer with a given rating.

Under the pricing measure the generator is \(\tilde Q = U Q\) with \(U = \operatorname{diag}(\mu)\) the risk premia (premia, ones if omitted). With recovery of treasury \(\delta\),

\[P^d(0, T) = e^{-rT}\,[\delta + (1 - \delta)(1 - \tilde P_{i,D}(T))].\]
Parameters:
  • T (ArrayLike) – Maturities.

  • r (float) – Flat risk-free rate.

  • Q (ArrayLike) – Generator with default as the last, absorbing state.

  • rating (int) – Index of the issuer’s current rating.

  • recovery (float)

  • premia (ArrayLike | None) – Risk-premium multipliers per rating.

Return type:

ndarray | float

Returns:

float or ndarray

financialkit.credit.joint_default_probability(p1, p2, rho)[source]#

Probability that both names default by the horizon under a Gaussian copula.

Parameters:
  • p1 (ArrayLike) – Marginal default probabilities to the horizon.

  • p2 (ArrayLike) – Marginal default probabilities to the horizon.

  • rho (ArrayLike) – Copula correlation.

Return type:

ndarray | float

Returns:

float or ndarray

Examples

>>> round(joint_default_probability(0.05, 0.05, 0.0), 6)
0.0025
financialkit.credit.joint_migration_probabilities(row1, row2, rho)[source]#

Joint one-year migration probabilities of two obligors with asset correlation rho.

Each cell is a bivariate-normal rectangle probability between the two obligors’ thresholds, computed with scipy.stats.multivariate_normal.

Parameters:
  • row1 (ArrayLike) – Migration probabilities of each obligor.

  • row2 (ArrayLike) – Migration probabilities of each obligor.

  • rho (float) – Asset-return correlation.

Return type:

ndarray

Returns:

ndarray, shape (k1, k2) – Rows and columns sum to the two marginals.

financialkit.credit.kmv_default_point(short_term_debt, long_term_debt, weight=0.5)[source]#

Moody’s KMV default point: short-term debt plus half of long-term debt.

Parameters:
Return type:

ndarray | float

Returns:

float or ndarray

financialkit.credit.kmv_iterate(equity, D, r, T=1.0, dt=0.003968253968253968, sigma0=None, rtol=1e-08, max_iter=1000)[source]#

Crosbie and Bohn’s (2003) iterative estimate of asset values and volatility.

Start from a guess of \(\sigma_V\), invert the Merton equity equation each day for \(V_t\) (Newton’s method, vectorised over days), re-estimate \(\sigma_V\) from the implied asset log returns, and repeat until it stops changing. The drift is the mean log return plus \(\sigma_V^2/2\).

Parameters:
  • equity (ArrayLike) – Equity market values, one per period.

  • D (ArrayLike) – Default point(s), scalar or one per period.

  • r (float) – Risk-free rate.

  • T (float) – Horizon of the distance to default (and the option maturity).

  • dt (float) – Spacing of the observations in years.

  • sigma0 (float | None) – Starting asset volatility; defaults to equity volatility times E / (E + D).

  • rtol (float) – Relative change in \(\sigma_V\) that stops the iteration.

  • max_iter (int)

Return type:

KMVResult

Returns:

KMVResult

financialkit.credit.leland_model(V, coupon, r, sigma, tax_rate=0.35, bankruptcy_cost=0.5, default_barrier=None)[source]#

Debt, equity and firm value in Leland’s (1994) model.

Parameters:
  • V (float) – Unlevered asset value.

  • coupon (float) – Perpetual coupon per year.

  • r (float) – Risk-free rate.

  • sigma (float) – Asset volatility.

  • tax_rate (float) – Corporate tax rate \(\tau\).

  • bankruptcy_cost (float) – Fraction \(\alpha\) of assets lost in bankruptcy.

  • default_barrier (float | None) – Exogenous barrier; the endogenous \(V_B\) if omitted.

Return type:

LelandResult

Returns:

LelandResult

Examples

At the endogenous barrier equity is exactly worthless:

>>> res = leland_model(100.0, 5.0, 0.06, 0.2)
>>> round(res.default_barrier, 4)
40.625
>>> leland_model(res.default_barrier, 5.0, 0.06, 0.2).equity < 1e-12
True
financialkit.credit.leland_optimal_coupon(V, r, sigma, tax_rate=0.35, bankruptcy_cost=0.5)[source]#

The coupon that maximises firm value, by scipy.optimize.minimize_scalar().

Parameters:
Return type:

tuple[float, LelandResult]

Returns:

  • coupon (float)

  • result (LelandResult) – The firm at the optimal coupon.

financialkit.credit.linear_discriminant(x_good, x_bad)[source]#

Fisher’s linear discriminant between two groups of ratio vectors.

The weights \(w = S_W^{-1}(\bar x_g - \bar x_b)\) maximise the separation of the group means relative to the pooled within-group covariance \(S_W\); the cutoff is the midpoint of the projected means (equal priors). Weights are only defined up to scale.

Parameters:
  • x_good (ArrayLike) – Ratios of surviving and failed firms.

  • x_bad (ArrayLike) – Ratios of surviving and failed firms.

Return type:

tuple[ndarray, float]

Returns:

  • weights (ndarray, shape (d,))

  • cutoff (float) – Scores above the cutoff are classified as survivors.

financialkit.credit.merton_calibrate(E, sigma_E, D, T, r)[source]#

Solve for the asset value and volatility implied by equity and its volatility.

Solves \(E = C_{BS}(V)\) and \(\sigma_E E = N(d_1)\sigma_V V\) with scipy.optimize.least_squares().

Parameters:
  • E (float) – Market value of equity.

  • sigma_E (float) – Equity volatility.

  • D (float) – Face value and maturity of the debt, and the risk-free rate.

  • T (float) – Face value and maturity of the debt, and the risk-free rate.

  • r (float) – Face value and maturity of the debt, and the risk-free rate.

Return type:

MertonResult

Returns:

MertonResult

Examples

Hull, 10th ed., Example 24.3:

>>> m = merton_calibrate(3.0, 0.80, 10.0, 1.0, 0.05)
>>> round(m.asset_value, 2), round(m.asset_volatility, 4), round(m.debt, 2)
(12.4, 0.2123, 9.4)
financialkit.credit.merton_model(V, D, T, r, sigma_V)[source]#

Value equity and debt in Merton’s (1974) model.

Parameters:
  • V (float) – Current asset value.

  • D (float) – Face value of the zero-coupon debt.

  • T (float) – Debt maturity in years.

  • r (float) – Risk-free rate.

  • sigma_V (float) – Asset volatility.

Return type:

MertonResult

Returns:

MertonResult

Examples

Hull, 10th ed., Example 24.3: assets 12.40, volatility 21.23%, debt of 10 due in a year, 5% rate.

>>> m = merton_model(12.3954, 10.0, 1.0, 0.05, 0.2123)
>>> round(m.equity, 2), round(m.default_probability, 3)
(3.0, 0.127)
financialkit.credit.migration_thresholds(row)[source]#

Asset-return thresholds that reproduce one row of a transition matrix.

States are ordered best first and default last, as in the matrix. An asset return \(Z\) below thresholds[-1] means default, between thresholds[j] and thresholds[j - 1] means rating j.

Parameters:

row (ArrayLike) – Migration probabilities from the current rating.

Return type:

ndarray

Returns:

ndarray, shape (k + 1,) – Decreasing thresholds, from +inf down to -inf.

Examples

A BBB obligor defaults below about -2.91 standard deviations:

>>> from financialkit.credit.utils.matrices import creditmetrics_matrix
>>> round(float(migration_thresholds(creditmetrics_matrix()[3])[-2]), 2)
-2.91
financialkit.credit.normal_factor_quadrature(n=64)[source]#

Gauss-Hermite nodes and weights for \(E[g(M)]\), \(M \sim N(0, 1)\).

Parameters:

n (int) – Number of nodes.

Return type:

tuple[ndarray, ndarray]

Returns:

nodes, weights (ndarray, shape (n,)) – Weights sum to one.

Examples

>>> x, w = normal_factor_quadrature(20)
>>> round(float(w @ x**4), 10)
3.0
financialkit.credit.nth_to_default_probability(times, k, T)[source]#

Probability that at least k names default by T, from simulated default times.

Parameters:
Return type:

ndarray | float

Returns:

float or ndarray

financialkit.credit.one_factor_loss_distribution(p, rho, n_obligors, lgd=1.0, n_nodes=128)[source]#

Exact loss distribution of a finite homogeneous portfolio, by factor quadrature.

\(P(K = k) = E_M[\binom{n}{k} p(M)^k (1 - p(M))^{n - k}]\), the expectation over \(M\) computed with Gauss-Hermite quadrature.

Parameters:
  • p (float)

  • rho (float)

  • n_obligors (int)

  • lgd (float) – Loss given default per obligor, as a fraction of its exposure.

  • n_nodes (int) – Quadrature nodes.

Return type:

LossDistribution

Returns:

LossDistribution – Losses as fractions of the portfolio notional.

financialkit.credit.panjer_recursion(severity, a, b, f0, n_max)[source]#

Aggregate-loss probabilities of an (a, b, 0) claim count with integer severities.

Parameters:
  • severity (ArrayLike) – severity[j] is the probability that one claim has size j.

  • a (float) – Panjer class parameters: Poisson (0, mu), negative binomial (p, (alpha - 1) p) with success probability 1 - p.

  • b (float) – Panjer class parameters: Poisson (0, mu), negative binomial (p, (alpha - 1) p) with success probability 1 - p.

  • f0 (float) – Probability of a zero aggregate loss.

  • n_max (int) – Largest aggregate loss computed.

Return type:

ndarray

Returns:

ndarray, shape (n_max + 1,)

Examples

A Poisson count with unit claims is just the Poisson law:

>>> from scipy import stats
>>> f = panjer_recursion([0.0, 1.0], 0.0, 2.0, float(np.exp(-2.0)), 10)
>>> np.allclose(f, stats.poisson.pmf(np.arange(11), 2.0))
True
financialkit.credit.regularize_generator(Q, method='diagonal')[source]#

Turn a candidate generator into a valid one.

  • "diagonal" (Israel, Rosenthal and Wei 2001): set negative off-diagonal entries to zero and absorb the change in the diagonal.

  • "weighted" (Kreinin and Sidelnikova 2001): set them to zero and take the excess from every remaining non-zero entry of the row in proportion to its absolute value.

Parameters:
Return type:

ndarray

Returns:

ndarray, shape (k, k)

financialkit.credit.risky_zero_coupon(T, r, hazard, recovery=0.4, convention='rmv')[source]#

Defaultable zero-coupon bond with a constant hazard rate, under three recovery conventions.

  • "rmv" (recovery of market value): \(e^{-(r + \lambda (1 - R)) T}\);

  • "rt" (recovery of treasury): \(e^{-rT}[R + (1 - R) e^{-\lambda T}]\);

  • "rfv" (recovery of face value, paid at default): \(e^{-(r + \lambda) T} + R \frac{\lambda}{r + \lambda}(1 - e^{-(r + \lambda) T})\).

Parameters:
  • T (ArrayLike) – Maturity, flat risk-free rate, constant intensity and recovery fraction.

  • r (ArrayLike) – Maturity, flat risk-free rate, constant intensity and recovery fraction.

  • hazard (ArrayLike) – Maturity, flat risk-free rate, constant intensity and recovery fraction.

  • recovery (ArrayLike) – Maturity, flat risk-free rate, constant intensity and recovery fraction.

  • convention (str)

Return type:

ndarray | float

Returns:

float or ndarray

Examples

With no recovery the three conventions agree:

>>> [round(risky_zero_coupon(5.0, 0.05, 0.02, 0.0, c), 6) for c in ("rmv", "rt", "rfv")]
[0.704688, 0.704688, 0.704688]
financialkit.credit.simulate_migrations(P, ratings, rho, n_sims, rng=None)[source]#

Simulate end-of-year ratings with one-factor Gaussian asset correlation.

Asset returns are \(Z_i = \sqrt\rho\, M + \sqrt{1 - \rho}\,\varepsilon_i\).

Parameters:
Return type:

ndarray

Returns:

ndarray of int, shape (n_sims, m)

financialkit.credit.simulate_one_factor_defaults(p, rho, n_obligors, n_sims, rng=None)[source]#

Number of defaults per scenario in a homogeneous one-factor portfolio.

Draws the factor, then the conditionally independent defaults as a binomial count.

Parameters:
Return type:

ndarray

Returns:

ndarray of int, shape (n_sims,)

financialkit.credit.spread_from_hazard(hazard, recovery=0.4)[source]#

Inverse credit triangle, \(s = \lambda (1 - R)\).

Parameters:
Return type:

ndarray | float

Returns:

float or ndarray

financialkit.credit.tranche_loss(L, attachment, detachment)[source]#

Tranche loss as a fraction of tranche notional.

Parameters:
  • L (ArrayLike) – Portfolio loss as a fraction of portfolio notional.

  • attachment (float)

  • detachment (float)

Return type:

ndarray | float

Returns:

float or ndarray

Examples

>>> tranche_loss([0.01, 0.05, 0.2], 0.03, 0.07).tolist()
[0.0, 0.5, 1.0]
financialkit.credit.tranche_spread(attachment, detachment, curve, rho, maturity, r, recovery=0.4, frequency=4, n_names=None)[source]#

Par spread of a synthetic CDO tranche (Hull and White 2004).

Premiums accrue on the expected outstanding tranche notional, averaged over each period, and protection pays the increase in expected tranche loss, discounted from mid-period.

Parameters:
  • attachment (float)

  • detachment (float)

  • curve (SurvivalCurve) – Survival curve shared by every name in the pool.

  • rho (float) – Copula correlation.

  • maturity (float)

  • r (float) – Flat risk-free rate.

  • recovery (float)

  • frequency (int) – Premium payments per year.

  • n_names (int | None) – Pool size; the large homogeneous pool if omitted.

Return type:

TrancheResult

Returns:

TrancheResult

financialkit.credit.transition_from_generator(Q, t=1.0)[source]#

Transition matrix \(P(t) = e^{Qt}\) by scipy.linalg.expm().

Parameters:
Return type:

ndarray

Returns:

ndarray, shape (k, k) or (n_t, k, k)

Examples

>>> Q = np.array([[-0.1, 0.1], [0.0, 0.0]])
>>> bool(np.isclose(transition_from_generator(Q, 2.0)[0, 1], 1 - np.exp(-0.2)))
True
financialkit.credit.upfront_to_spread(upfront, coupon, maturity, r, recovery=0.4, frequency=4, steps_per_year=52)[source]#

ISDA quote conversion: the flat-hazard par spread equivalent to an upfront.

Finds the flat hazard rate that reproduces upfront at coupon and returns that curve’s par spread.

Parameters:
Return type:

float

Returns:

float

financialkit.credit.vasicek_cdf(x, p, rho)[source]#

Distribution function of the large-portfolio default fraction.

Parameters:
Return type:

ndarray | float

Returns:

float or ndarray

Examples

The median default rate lies below the mean p (the law is right-skewed):

>>> vasicek_cdf(0.02, 0.02, 0.2) > 0.5
True
financialkit.credit.vasicek_pdf(x, p, rho)[source]#

Density of the large-portfolio default fraction.

Parameters:
Return type:

ndarray | float

Returns:

float or ndarray

financialkit.credit.vasicek_quantile(alpha, p, rho)[source]#

Quantile of the large-portfolio default fraction (the worst-case default rate).

\[N\left(\frac{N^{-1}(p) + \sqrt\rho\, N^{-1}(\alpha)}{\sqrt{1 - \rho}}\right)\]
Parameters:
Return type:

ndarray | float

Returns:

float or ndarray

Examples

>>> x = vasicek_quantile(0.999, 0.01, 0.2)
>>> round(vasicek_cdf(x, 0.01, 0.2), 10)
0.999