financialkit.risk#
Value at risk by historical simulation, normal and Student-t closed
forms, RiskMetrics delta-normal, the Cornish-Fisher expansion,
full-revaluation Monte Carlo through
financialkit.integrators.correlated_increments(),
Boudoukh-Richardson-Whitelaw age weighting and filtered historical
simulation; Roy’s safety-first criterion; Acerbi-Tasche expected
shortfall with normal and Student-t closed forms, the coherence axioms
and Euler risk allocation; block maxima (scipy.stats.genextreme) and
peaks over threshold (scipy.stats.genpareto); Kupiec and
Christoffersen backtests and the Basel traffic light; Gaussian and t
copulas with tail dependence; drawdowns and the Calmar ratio.
financialkit.risk: value at risk, expected shortfall, extreme values, backtests, copulas and drawdowns.
Value at risk by historical simulation, normal and Student-t closed
forms, RiskMetrics delta-normal, the Cornish-Fisher expansion, full
revaluation Monte Carlo (through financialkit.integrators),
Boudoukh-Richardson-Whitelaw age weighting and filtered historical
simulation; Roy’s safety-first criterion; Acerbi-Tasche expected
shortfall, the coherence axioms and Euler risk allocation; block maxima
and peaks over threshold (scipy.stats.genextreme,
scipy.stats.genpareto); Kupiec and Christoffersen backtests and
the Basel traffic light; Gaussian and t copulas with tail dependence;
drawdowns and the Calmar ratio. Profits and losses go in with losses
negative, and every risk figure comes out as a positive loss.
- class financialkit.risk.AllocationResult(contributions, total, measure)[source]#
Bases:
objectEuler allocation of a portfolio risk figure to its positions.
- contributions: ndarray#
Risk contribution of each position; they sum to
total.- Type:
ndarray, shape (d,)
Contributions as fractions of the total.
- Type:
ndarray
- class financialkit.risk.BacktestResult(statistic, p_value, df, n_exceptions, n_obs, expected)[source]#
Bases:
objectA likelihood-ratio test of VaR exceptions.
- Parameters:
- class financialkit.risk.CoherenceResult(monotonicity, subadditivity, positive_homogeneity, translation_invariance, rho_x, rho_y, rho_sum)[source]#
Bases:
objectWhich of Artzner et al.’s four axioms a risk measure satisfied on a test pair.
- Parameters:
- class financialkit.risk.DrawdownResult(wealth, peak, drawdown, max_drawdown, peak_index, trough_index, recovery_index)[source]#
Bases:
objectThe drawdown path of a wealth index and its summary statistics.
- Parameters:
- drawdown: ndarray#
Fractional fall from the peak,
1 - wealth / peak(non-negative).- Type:
ndarray, shape (n + 1,)
- class financialkit.risk.ExpectedShortfall(alpha=0.99)[source]#
Bases:
RiskMeasureAcerbi-Tasche expected shortfall as a
RiskMeasure.- Parameters:
alpha (float)
- class financialkit.risk.GEVResult(xi, mu, sigma, block_size, maxima=<factory>)[source]#
Bases:
objectA generalized extreme value fit to block maxima of losses.
- class financialkit.risk.GPDResult(xi, beta, threshold, n_exceed, n_obs, excesses=<factory>)[source]#
Bases:
objectA generalized Pareto fit to losses above a threshold.
- class financialkit.risk.RiskMeasure(alpha=0.99)[source]#
Bases:
ABCA map from a random profit and loss, given by scenarios, to a capital figure.
- Parameters:
alpha (
float) – Confidence level in(0, 1).
- class financialkit.risk.TrafficLightResult(zone, n_exceptions, cumulative_probability, multiplier)[source]#
Bases:
objectThe Basel Committee’s (1996) backtesting zone for an internal model.
- cumulative_probability: float#
Binomial probability of at most
n_exceptionsexceptions for an accurate model.
- class financialkit.risk.VaRResult(var, es, alpha, method, scenarios, weights, ci=(nan, nan))[source]#
Bases:
objectValue at risk and expected shortfall estimated from scenarios.
- Parameters:
- ci: tuple[float, float] = (nan, nan)#
Order-statistic confidence interval for the VaR (Monte Carlo only).
- class financialkit.risk.ValueAtRisk(alpha=0.99)[source]#
Bases:
RiskMeasureVaR as a
RiskMeasureon equally likely scenarios.Examples
>>> ValueAtRisk(0.95)([-5.0, -1.0, 0.0, 1.0, 2.0] * 4) 5.0
- Parameters:
alpha (float)
- financialkit.risk.apply_marginals(u, marginals)[source]#
Sklar’s construction: give each copula coordinate its own marginal law.
- financialkit.risk.basel_traffic_light(n_exceptions, n_obs=250, alpha=0.99)[source]#
The Basel Committee’s (1996) green, yellow and red zones.
The zone is set by the binomial probability that an accurate model produces at most
n_exceptionsexceptions: green below 95%, red from 99.99%, yellow in between. For the regulatory setting (250 days, 99% VaR) that is 0-4 green, 5-9 yellow and 10 or more red, and the capital multiplier is 3 plus the Committee’s plus factor.- Parameters:
- Return type:
- Returns:
TrafficLightResult
Examples
>>> res = basel_traffic_light(6) >>> res.zone, res.multiplier ('yellow', 3.5)
- financialkit.risk.block_maxima(pnl, block_size)[source]#
Largest loss in each consecutive block (an incomplete last block is dropped).
- financialkit.risk.calmar_ratio(returns, periods_per_year=252, log=False)[source]#
Young’s Calmar ratio: compound annual growth rate over maximum drawdown.
Young used the trailing 36 months; pass that window’s returns to reproduce his convention.
- Parameters:
- Return type:
- Returns:
float –
infif there was no drawdown.
Examples
>>> round(calmar_ratio([0.10, -0.05, 0.08, 0.02], periods_per_year=4), 4) 3.0234
- financialkit.risk.check_coherence(measure, X, Y, lam=2.0, c=1.0)[source]#
Test the four coherence axioms of
measureon two positions.- Parameters:
measure (
RiskMeasure)X (
ArrayLike) – Profits and losses of two positions in the samenequally likely states of the world.Y (
ArrayLike) – Profits and losses of two positions in the samenequally likely states of the world.lam (
float) – Positive scale for the homogeneity check.c (
float) – Cash amount for the translation check.
- Return type:
- Returns:
CoherenceResult – A failed axiom is a counterexample; passing on one pair proves nothing.
- financialkit.risk.christoffersen_independence(hits, alpha=0.99)[source]#
Christoffersen’s test that exceptions do not cluster.
Fits a two-state Markov chain to the hit sequence, with \(\pi_{01} = P(\text{hit} \mid \text{no hit yesterday})\) and \(\pi_{11} = P(\text{hit} \mid \text{hit yesterday})\), and tests \(\pi_{01} = \pi_{11}\) against a single rate (\(\chi^2_1\)).
- Parameters:
- Return type:
- Returns:
BacktestResult
- financialkit.risk.christoffersen_test(hits, alpha=0.99)[source]#
Christoffersen’s conditional-coverage test, \(LR_{cc} = LR_{uc} + LR_{ind} \sim \chi^2_2\).
- Parameters:
- Return type:
- Returns:
BacktestResult
- financialkit.risk.copula_kendall_tau(rho)[source]#
Kendall’s tau of a Gaussian or t copula, \(\tfrac{2}{\pi}\arcsin\rho\).
Examples
>>> round(copula_kendall_tau(0.5), 4) 0.3333
- financialkit.risk.cornish_fisher_var(mu, sigma, skew, excess_kurtosis, alpha=0.99)[source]#
VaR from the Cornish-Fisher expansion of the quantile in skewness and kurtosis.
With \(z = \Phi^{-1}(1 - \alpha)\), skewness \(S\) and excess kurtosis \(K\), the adjusted quantile is
\[z_{cf} = z + \frac{(z^2 - 1) S}{6} + \frac{(z^3 - 3z) K}{24} - \frac{(2z^3 - 5z) S^2}{36},\]and \(\mathrm{VaR} = -(\mu + \sigma z_{cf})\).
- Parameters:
- Return type:
- Returns:
float or ndarray
Examples
With no skew and no excess kurtosis it is the normal VaR.
>>> round(cornish_fisher_var(0.0, 1.0, 0.0, 0.0, 0.99), 4) 2.3263 >>> round(cornish_fisher_var(0.0, 1.0, -0.5, 3.0, 0.99), 4) 3.3013
- financialkit.risk.delta_normal_var(exposures, cov, alpha=0.99, horizon=1.0)[source]#
RiskMetrics delta-normal portfolio VaR, \(q_\alpha \sqrt{h\, w^\top \Sigma w}\).
- Parameters:
- Return type:
- Returns:
float
Examples
Hull, 10th ed., Ch. 22: $10 million in Microsoft (2% daily vol) and $5 million in AT&T (1%), correlation 0.3. The ten-day 99% VaR is about $1.62 million, against $1.83 million if the two VaRs are added.
>>> import numpy as np >>> cov = np.array([[0.02**2, 0.3 * 0.02 * 0.01], [0.3 * 0.02 * 0.01, 0.01**2]]) >>> round(delta_normal_var([10e6, 5e6], cov, 0.99, horizon=10)) 1620114
- financialkit.risk.drawdowns(returns, log=False)[source]#
Drawdown path and maximum drawdown of a compounded return series.
- Parameters:
- Return type:
- Returns:
DrawdownResult
Examples
Up 50%, down 50%, up 20%: the peak of 1.5 falls to 0.75, a 50% drawdown.
>>> res = drawdowns([0.5, -0.5, 0.2]) >>> res.max_drawdown, res.peak_index, res.trough_index, res.recovery_index (0.5, 1, 2, -1)
- financialkit.risk.empirical_tail_dependence(u, v, q=0.05, tail='lower')[source]#
Finite-level tail dependence \(P(V < q \mid U < q)\) (or the upper analogue).
- financialkit.risk.euler_allocation(weights, cov, alpha=0.99, measure='var')[source]#
Euler contributions to the volatility, normal VaR or normal ES of a portfolio.
- Parameters:
- Return type:
- Returns:
AllocationResult
Examples
Hull, 10th ed., Ch. 22’s Microsoft/AT&T portfolio: the one-day 99% VaR of about 512,300 splits into component VaRs that sum to it.
>>> import numpy as np >>> cov = np.array([[0.02**2, 0.3 * 0.02 * 0.01], [0.3 * 0.02 * 0.01, 0.01**2]]) >>> res = euler_allocation([10e6, 5e6], cov, 0.99) >>> np.round(res.contributions), round(res.total) (array([454226., 58099.]), 512325)
- financialkit.risk.expected_shortfall(pnl, alpha=0.99)[source]#
Acerbi-Tasche expected shortfall of equally likely scenarios.
- Parameters:
- Return type:
- Returns:
float – A positive loss, never below the VaR at the same level.
Examples
With 100 outcomes and a 2.5% tail the estimator takes the two worst losses in full and half of the third.
>>> import numpy as np >>> expected_shortfall(-np.arange(1.0, 101.0), 0.975) 99.2
- financialkit.risk.filtered_historical_var(pnl, sigma, sigma_forecast, alpha=0.99)[source]#
Filtered historical simulation (Barone-Adesi, Giannopoulos and Vosper 1999).
Each past outcome is divided by the volatility of its day, giving roughly i.i.d. standardized residuals \(z_t = x_t / \sigma_t\), and rescaled by tomorrow’s forecast: the scenarios are \(\hat\sigma_{n+1} z_t\). The empirical shape of the tail is kept while its scale follows current volatility.
- financialkit.risk.fit_gev(pnl, block_size)[source]#
Fit the generalized extreme value law to block maxima of the losses.
- financialkit.risk.fit_pot(pnl, threshold)[source]#
Fit a generalized Pareto law to the losses above
threshold.
- financialkit.risk.gaussian_copula_sample(corr, n, rng=None)[source]#
Draw from the Gaussian copula with correlation matrix
corr.
- financialkit.risk.gev_return_level(fit, k)[source]#
Loss level exceeded by the block maximum once every
kblocks on average.- Parameters:
- Return type:
- Returns:
float or ndarray
Examples
For a Gumbel law (\(\xi = 0\)) the 100-block level is \(\mu - \sigma \ln(-\ln 0.99)\).
>>> round(gev_return_level(GEVResult(xi=0.0, mu=0.0, sigma=1.0, block_size=21), 100), 4) 4.6001
- financialkit.risk.historical_var(pnl, alpha=0.99)[source]#
Historical-simulation VaR and ES: past outcomes, equally likely.
- Parameters:
- Return type:
- Returns:
VaRResult
Examples
With 100 outcomes the 95% VaR is the fifth-worst loss.
>>> import numpy as np >>> historical_var(-np.arange(1.0, 101.0), 0.95).var 96.0
- financialkit.risk.hybrid_var(pnl, alpha=0.99, lam=0.98)[source]#
Boudoukh-Richardson-Whitelaw age-weighted historical simulation.
The outcome
idays old gets probability proportional to \(\lambda^i\), so recent days count more and the VaR reacts to a change in volatility within weeks instead of a whole window;lam = 1is plain historical simulation.
- financialkit.risk.kupiec_test(hits, alpha=0.99)[source]#
Kupiec’s proportion-of-failures test of unconditional coverage.
\[LR_{uc} = -2 \ln \frac{(1 - p)^{n - x} p^x}{(1 - \hat p)^{n - x} \hat p^x}, \qquad \hat p = x / n, \quad LR_{uc} \sim \chi^2_1.\]- Parameters:
- Return type:
- Returns:
BacktestResult
Examples
Twelve exceptions of a 99% VaR in 250 days are far too many.
>>> res = kupiec_test([True] * 12 + [False] * 238) >>> round(res.statistic, 2), res.reject() (19.02, True)
- financialkit.risk.mean_excess(pnl, thresholds)[source]#
Empirical mean excess function \(e(u) = E[L - u \mid L > u]\) of the losses.
For a generalized Pareto tail it is linear in \(u\) with slope \(\xi / (1 - \xi)\), which is how a threshold is chosen: where the plot turns straight.
- financialkit.risk.monte_carlo_var(pnl_fn, cov, alpha=0.99, mean=None, horizon=1.0, n_paths=10000, ci_level=0.95, rng=None)[source]#
Full-revaluation Monte Carlo VaR (Pritsker 1997).
Draws joint normal risk-factor moves \(\Delta X \sim N(\mu h, \Sigma h)\) through
financialkit.integrators.correlated_increments(), revalues the portfolio in every scenario withpnl_fnand reads the VaR and ES off the simulated distribution, with an exact order-statistic confidence interval for the VaR.- Parameters:
pnl_fn (
Callable[[ndarray],ArrayLike]) –pnl_fn(dX) -> pnlmaps factor moves of shape(n_paths, d)to the portfolio’s profit and loss, shape(n_paths,).cov (
ArrayLike) – Covariance of the one-period factor moves.alpha (
float) – Confidence level.mean (
ArrayLike|None) – Mean one-period factor move; zero if omitted.horizon (
float) – Number of periods.n_paths (
int) – Number of scenarios.ci_level (
float) – Coverage of the confidence interval stored inresult.ci.
- Return type:
- Returns:
VaRResult
- financialkit.risk.parametric_es(mu, sigma, alpha=0.99, horizon=1.0, distribution='normal', df=None)[source]#
Expected shortfall of a normal or (unit-variance-scaled) Student-t profit and loss.
Normal: \(-\mu h + \sigma\sqrt{h}\, \varphi(z_\alpha) / (1 - \alpha)\). Student-t with \(\nu\) degrees of freedom and \(t_\alpha = t_\nu^{-1}(\alpha)\): \(-\mu h + \sigma\sqrt{h}\sqrt{(\nu - 2)/\nu}\; \frac{g_\nu(t_\alpha)}{1 - \alpha} \frac{\nu + t_\alpha^2}{\nu - 1}\) (McNeil, Frey and Embrechts, Example 2.15).
- Parameters:
- Return type:
- Returns:
float or ndarray
Examples
The Basel 97.5% ES of a standard normal is close to its 99% VaR.
>>> round(parametric_es(0.0, 1.0, 0.975), 4) 2.3378
- financialkit.risk.parametric_var(mu, sigma, alpha=0.99, horizon=1.0, distribution='normal', df=None)[source]#
Parametric VaR of a normal or Student-t profit and loss.
\(\mathrm{VaR} = -\mu h + \sigma \sqrt{h}\, q_\alpha\), with \(q_\alpha\) the standard normal quantile, or the Student-t quantile rescaled to unit variance, \(t_\nu^{-1}(\alpha)\sqrt{(\nu - 2)/\nu}\).
- Parameters:
mu (
ArrayLike) – Mean and standard deviation of the one-period profit and loss.sigma (
ArrayLike) – Mean and standard deviation of the one-period profit and loss.alpha (
ArrayLike) – Confidence level.horizon (
ArrayLike) – Number of periods; the mean scales withhand the volatility withsqrt(h)(independent increments).distribution (
str)df (
float|None) – Degrees of freedom (> 2) fordistribution="t".
- Return type:
- Returns:
float or ndarray
Examples
Hull, 10th ed., Ch. 22: a $10 million position with a 2% daily volatility has a one-day 99% VaR of about $465,300 and a ten-day VaR of about $1.47 million.
>>> round(parametric_var(0.0, 200_000.0, 0.99)) 465270 >>> round(parametric_var(0.0, 200_000.0, 0.99, horizon=10)) 1471312
- financialkit.risk.pot_es(fit, alpha=0.99)[source]#
Peaks-over-threshold expected shortfall (finite only for
xi < 1).
- financialkit.risk.pot_var(fit, alpha=0.99)[source]#
Peaks-over-threshold VaR (valid for
1 - alphabelow the exceedance rate).
- financialkit.risk.pseudo_observations(x)[source]#
Column-wise ranks scaled into the open unit interval,
rank / (n + 1).The empirical copula of the data: it discards the marginals and keeps only the dependence, as Sklar’s theorem says one can.
- financialkit.risk.rolling_historical_var(pnl, window=250, alpha=0.99)[source]#
Out-of-sample historical VaR forecasts from a rolling window.
The forecast for day
tuses dayst - windowtot - 1only, as in the Basel Committee’s (1996) backtesting framework.
- financialkit.risk.safety_first_portfolio(mu, cov, threshold=0.0)[source]#
Fully invested weights maximising Roy’s ratio.
- financialkit.risk.safety_first_ratio(mu, sigma, threshold=0.0)[source]#
Roy’s ratio \((\mu - d) / \sigma\).
- Parameters:
- Return type:
- Returns:
float or ndarray
Examples
>>> safety_first_ratio(0.08, 0.15, 0.02) 0.4
- financialkit.risk.scenario_es_allocation(pnl, alpha=0.99)[source]#
Euler contributions to expected shortfall estimated from scenarios.
Position \(i\)’s contribution is minus its mean profit and loss over the \(k = \lceil n(1 - \alpha) \rceil\) scenarios in which the portfolio does worst; the contributions add up to the portfolio’s tail mean.
- Parameters:
- Return type:
- Returns:
AllocationResult
- financialkit.risk.shortfall_probability(mu, sigma, threshold=0.0, bound='chebyshev')[source]#
Probability that the return falls below the disaster level.
- Parameters:
- Return type:
- Returns:
float or ndarray
Examples
>>> round(shortfall_probability(0.08, 0.15, 0.02, "normal"), 4) 0.3446 >>> shortfall_probability(0.08, 0.03, 0.02) 0.25
- financialkit.risk.t_copula_sample(corr, df, n, rng=None)[source]#
Draw from the Student-t copula: correlated normals over a shared \(\sqrt{W / \nu}\).
- financialkit.risk.t_tail_dependence(rho, df)[source]#
Coefficient of (upper = lower) tail dependence of the bivariate t copula.
- Parameters:
- Return type:
- Returns:
float or ndarray
Examples
Demarta and McNeil (2005), Table 1: \(\nu = 4\), \(\rho = 0.5\).
>>> round(t_tail_dependence(0.5, 4), 2) 0.25 >>> t_tail_dependence(0.5, float("inf")) 0.0
- financialkit.risk.weighted_tail(pnl, alpha, weights=None)[source]#
VaR and expected shortfall of a discrete profit-and-loss distribution.
- Parameters:
- Return type:
- Returns:
var, es (float) – Positive losses.
es >= varalways.
Examples
Ten equally likely outcomes, a 15% tail: the VaR is the second-worst loss and the ES averages the worst loss with half of the second.
>>> weighted_tail([-10, -6, -4, -1, 0, 1, 2, 3, 4, 5], 0.85) (6.0, 8.666666666666666)