Source code for financialkit.credit.utils.numerics
"""Numerics behind the portfolio-loss models: Panjer's recursion and factor quadrature.
Panjer (1981) showed that when the claim count :math:`N` satisfies
:math:`P(N = n) = (a + b/n) P(N = n - 1)` (Poisson, binomial and negative
binomial do) and severities are integers, the aggregate-loss
probabilities obey
.. math:: f_n = \\frac{1}{1 - a s_0} \\sum_{j=1}^{n} \\left(a + \\frac{b j}{n}\\right) s_j f_{n-j},
an :math:`O(n^2)` recursion with no Fourier transform or simulation.
:func:`normal_factor_quadrature` gives Gauss-Hermite nodes and weights
for expectations over a standard-normal common factor.
References: H. H. Panjer, "Recursive Evaluation of a Family of Compound
Distributions," *ASTIN Bull.* 12 (1981) 22-26.
"""
from __future__ import annotations
import numpy as np
from numpy.polynomial.hermite_e import hermegauss
from numpy.typing import ArrayLike
__all__ = ["panjer_recursion", "normal_factor_quadrature"]
[docs]
def panjer_recursion(severity: ArrayLike, a: float, b: float, f0: float, n_max: int) -> np.ndarray:
"""Aggregate-loss probabilities of an (a, b, 0) claim count with integer severities.
Parameters
----------
severity : array_like, shape (m,)
``severity[j]`` is the probability that one claim has size ``j``.
a, 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.
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
"""
s = np.zeros(n_max + 1)
sev = np.asarray(severity, dtype=np.float64)[: n_max + 1]
s[: sev.size] = sev
f = np.zeros(n_max + 1)
f[0] = f0
j = np.arange(1, n_max + 1)
c = 1.0 / (1.0 - a * s[0])
for n in range(1, n_max + 1):
jj = j[:n]
f[n] = c * np.sum((a + b * jj / n) * s[jj] * f[n - jj])
return f
[docs]
def normal_factor_quadrature(n: int = 64) -> tuple[np.ndarray, np.ndarray]:
"""Gauss-Hermite nodes and weights for :math:`E[g(M)]`, :math:`M \\sim N(0, 1)`.
Parameters
----------
n : int
Number of nodes.
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
"""
x, w = hermegauss(n)
return x, w / w.sum()