Source code for extremeloss.evt.gpd

from __future__ import annotations

import math

import numpy as np
from scipy.stats import genpareto

from ..results import GPDFit
from ..utils.validation import as_1d_float_array, validate_q, validate_threshold


[docs] def fit_gpd(excesses, threshold: float = 0.0, method: str = "mle") -> GPDFit: """Fit a generalized Pareto distribution to excess losses.""" if method != "mle": raise ValueError("only method='mle' is currently supported") validate_threshold(threshold) x = as_1d_float_array(excesses, name="excesses") if np.any(x <= 0.0): raise ValueError("excesses must be strictly positive") xi_hat, loc_hat, beta_hat = genpareto.fit(x, floc=0.0) if loc_hat != 0.0: raise RuntimeError("GPD fit returned nonzero location despite floc=0") return GPDFit( threshold=float(threshold), xi=float(xi_hat), beta=float(beta_hat), exceedance_fraction=1.0, n_exceedances=int(x.size), fit_method=method, )
def gpd_tail_probability( x: float, threshold: float, xi: float, beta: float, exceedance_fraction: float, ) -> float: validate_threshold(threshold) if beta <= 0.0: raise ValueError("beta must be positive") if x <= threshold: return float(min(1.0, exceedance_fraction)) y = (x - threshold) / beta if abs(xi) < 1e-10: surv = math.exp(-y) else: term = 1.0 + xi * y if term <= 0.0: return 0.0 surv = term ** (-1.0 / xi) return float(exceedance_fraction * surv) def gpd_var( p: float, threshold: float, xi: float, beta: float, exceedance_fraction: float, ) -> float: validate_q(p) validate_threshold(threshold) if beta <= 0.0: raise ValueError("beta must be positive") if exceedance_fraction <= 0.0 or exceedance_fraction > 1.0: raise ValueError("exceedance_fraction must lie in (0, 1]") tail_prob = 1.0 - p if tail_prob >= exceedance_fraction: raise ValueError( "p is not far enough into the tail for the specified threshold and exceedance_fraction" ) ratio = tail_prob / exceedance_fraction if abs(xi) < 1e-10: return float(threshold + beta * math.log(1.0 / ratio)) return float(threshold + (beta / xi) * (ratio ** (-xi) - 1.0)) def gpd_tvar( p: float, threshold: float, xi: float, beta: float, exceedance_fraction: float, ) -> float: if xi >= 1.0: raise ValueError("TVaR is infinite for xi >= 1") var_p = gpd_var(p, threshold, xi, beta, exceedance_fraction) return float((var_p + beta - xi * threshold) / (1.0 - xi))