Gerber and compound Poisson stop loss premiums

Reproducing Gerber (1982)

Author

Stephen J. Mildenhall

Published

2026-10-22 19:55 commit b5ca906+

1 Context and problem

Gerber (1982) is about evaluating a stop loss premium

\[\pi(t) = \mathsf E[(S - t)_+] = \mathsf E[S] - \mathsf E[S \wedge t]\]

when the aggregate \(S\) is compound Poisson with a continuous severity. In 1982 the obstacle was that the recursion available at the time, Panjer’s, needs an arithmetic severity, so the continuous severity has to be discretized onto a span \(h\) first, and the discretization changes the answer.

The paper’s contribution is to bracket the error rather than hope about it. It sets out three ways to put a continuous severity onto a lattice of span \(h\):

  1. move all the mass in a cell to its lower endpoint, which makes the aggregate smaller in the relevant order and produces a lower bound on the stop loss premium,
  2. move it to the upper endpoint, giving an upper bound,
  3. split the mass between the two endpoints so that the cell’s mean is preserved, the method of matching moments.

Tables 1 to 6 then show all three at \(h = 1\) and \(h = 0.1\) against an exact column, for expected claim counts 1, 10 and 100. The paper’s conclusion is that the bounds do bracket, that the upper bound is the better of the two, and that matching moments beats both by a wide margin: at \(\lambda = 100\) it does as well at \(h = 1\) as the bounds do at \(h = 0.1\).

Forty years later the interesting content is not the ranking, which is settled, but the exact column. It is 28 published four figure numbers for a compound Poisson with a continuous severity, computed by somebody with no FFT, and that makes it a good external check.

2 Paper solution and gold standard

Frequency is Poisson with mean \(\lambda\) and severity is uniform on \((1, 3)\), so \(\mathsf E[X] = 2\) and \(\mathsf E[S] = 2\lambda\). Tables 1, 2 and 3, Figure 1, tabulate \(\pi(t)\) for \(\lambda = 1, 10, 100\) at the retentions Table 1 lists.

The exact column is not a closed form. It is the paper’s own method (c) at \(h = 0.01\), and the paper says so: “column 2 gives the ‘exact’ stop-loss premium as calculated by method (c) with \(h = 0.01\). (A comparison with the results of methods (a) and (b) with \(h = 0.01\) has convinced the author that the displayed digits are accurate.)” So the target is four significant figures, and the reproduction is a comparison of two independent approximations rather than a check against truth.

Figure 1: Gerber (1982), Tables 1, 2, and 3.
%xmode minimal
import numpy as np
import pandas as pd
from bookstyle import GT, build, show

# Transcribed from the 'Exact value' column of Tables 1, 2 and 3.
PAPER = {
    1: {0: 2.0, 2: 8.277e-1, 4: 2.689e-1, 6: 7.184e-2, 8: 1.627e-2,
        10: 3.253e-3, 12: 5.815e-4, 14: 9.346e-5, 16: 1.366e-5,
        18: 1.840e-6, 20: 2.302e-7},
    10: {15: 5.757, 20: 2.626, 25: 9.321e-1, 30: 2.563e-1, 35: 5.507e-2,
         40: 9.383e-3, 45: 1.289e-3, 50: 1.449e-4, 55: 1.355e-5, 60: 1.067e-6},
    100: {180: 2.177e1, 200: 8.304, 220: 1.959, 240: 2.647e-1, 260: 1.992e-2,
          280: 8.364e-4, 300: 1.994e-5},
}
GT(pd.DataFrame([{'table': t, 'lambda': lam, 'E[S]': 2 * lam,
                  'retentions': f'{min(v)} to {max(v)}', 'values': len(v)}
                 for t, (lam, v) in enumerate(PAPER.items(), start=1)]
                ).set_index('table'), float_format=0)
Exception reporting mode: Minimal
Table 1: The three published tables, their claim counts and the retentions each tabulates.
tablelambdaE[S]retentionsvalues
1120 to 2011
2102015 to 6010
3100200180 to 3007

The severity supplies an acceptance test before any aggregate is built. A uniform on \((1, 3)\) has mean 2 and variance \(4/12 = 1/3\), so \(\mathsf E[S] = 2\lambda\) and \(\mathsf{Var}[S] = \lambda\,\mathsf E[X^2] = \lambda(4 + 1/3)\) by the compound Poisson variance formula. Table 2 records the two severity moments the check uses.

GT(pd.DataFrame([
    {'quantity': 'severity mean', 'exact': 2.0},
    {'quantity': 'severity second moment', 'exact': 4 + 1 / 3},
]).set_index('quantity'), float_format=8)
Table 2: The first two moments of the uniform severity on \((1, 3)\).
quantityexact
severity mean2.00000000
severity second moment4.33333333

3 The aggregate reproduction

The severity is sev 2 * uniform + 1: a standard uniform scaled to width 2 and shifted to start at 1. Nothing else in the declaration varies across the three tables except \(\lambda\) and the grid, and Table 3 confirms all three builds before any premium is read.

def gerber(lam, bs, log2=18):
    return build(f'agg Gerber {lam} claims sev 2 * uniform + 1 poisson '
                 f'hints{{log2={log2}; bs={bs}}}')

a1 = gerber(1, 1 / 1024)
show(a1)
agg Gerber
  1 claim
  sev 2 * uniform + 1
  poisson
  hints{log2=18; bs=0.0009765625}
rows = []
for lam in (1, 10, 100):
    a = gerber(lam, 1 / 1024 if lam == 1 else 1 / 128 if lam == 10 else 1 / 64)
    rows.append({'lambda': lam, 'E[S] exact': 2 * lam, 'E[S] agg': a.est_m,
                 'sd exact': np.sqrt(lam * (4 + 1 / 3)), 'sd agg': a.est_sd,
                 'total mass': a.agg_density.sum()})
GT(pd.DataFrame(rows).set_index('lambda'),
   formatters={'E[S] exact': 6, 'E[S] agg': 6, 'sd exact': 6, 'sd agg': 6,
               'total mass': 12})
Table 3: The aggregate mean and standard deviation at each claim count, against the compound Poisson formulas.
lambdaE[S] exactE[S] aggsd exactsd aggtotal mass
12.0000002.0000002.0816662.0816661.000000000000
1020.00000020.0000006.5828066.5828141.000000000000
100200.000000200.00000020.81666020.8167581.000000000000

Now the tables, Table 4. The stop loss premium comes straight off density_df['lev'], since \(\pi(t) = \mathsf E[S] - \mathsf E[S \wedge t]\).

def stop_loss(lam, bs, log2=18):
    a = gerber(lam, bs, log2)
    return {t: a.est_m - float(a.density_df.loc[t, 'lev']) for t in PAPER[lam]}

FINE = {1: 1 / 1024, 10: 1 / 128, 100: 1 / 64}
rows = []
for lam, bs in FINE.items():
    got = stop_loss(lam, bs)
    for t, exact in PAPER[lam].items():
        rows.append({'lambda': lam, 't': t, 'paper exact': exact,
                     'aggregate': got[t], 'rel err': abs(got[t] / exact - 1)})
res = pd.DataFrame(rows)
GT(res.set_index(['lambda', 't']),
   formatters={'paper exact': '{x:.4e}', 'aggregate': '{x:.4e}',
               'rel err': '{x:.2e}'})
Table 4: All 28 published stop loss premiums against the rebuilt values, with the relative error on each.
lambdatpaper exactaggregaterel err
102.0000e+002.0000e+009.84e-14
28.2770e-018.2773e-013.47e-05
42.6890e-012.6891e-013.43e-05
67.1840e-027.1838e-022.83e-05
81.6270e-021.6267e-022.07e-04
103.2530e-033.2535e-031.58e-04
125.8150e-045.8146e-046.50e-05
149.3460e-059.3461e-058.90e-06
161.3660e-051.3664e-052.58e-04
181.8400e-061.8400e-061.75e-05
202.3020e-072.3017e-071.51e-04
10155.7570e+005.7565e+008.03e-05
202.6260e+002.6255e+001.78e-04
259.3210e-019.3214e-014.51e-05
302.5630e-012.5632e-018.21e-05
355.5070e-025.5073e-026.03e-05
409.3830e-039.3834e-033.90e-05
451.2890e-031.2887e-032.55e-04
501.4490e-041.4495e-043.53e-04
551.3550e-051.3552e-051.45e-04
601.0670e-061.0673e-062.35e-04
1001802.1770e+012.1773e+011.45e-04
2008.3040e+008.3045e+005.76e-05
2201.9590e+001.9591e+006.48e-05
2402.6470e-012.6470e-014.41e-06
2601.9920e-021.9919e-024.91e-05
2808.3640e-048.3651e-041.28e-04
3001.9940e-051.9938e-051.15e-04
GT(pd.DataFrame([
    {'lambda': lam, 'values compared': int((res['lambda'] == lam).sum()),
     'worst relative error': float(res[res['lambda'] == lam]['rel err'].max()),
     'inside 4 significant figures':
         int((res[res['lambda'] == lam]['rel err'] < 5e-4).sum())}
    for lam in (1, 10, 100)]
    + [{'lambda': 'all', 'values compared': len(res),
        'worst relative error': float(res['rel err'].max()),
        'inside 4 significant figures': int((res['rel err'] < 5e-4).sum())}]
).set_index('lambda'), formatters={'worst relative error': '{x:.2e}'})
Table 5: How many values reproduce inside the four significant figures the paper prints, table by table.
lambdavalues comparedworst relative errorinside 4 significant figures
1112.58e-0411
10103.53e-0410
10071.45e-047
all283.53e-0428

By Table 5 all 28 published values reproduce inside the four significant figures the paper prints, over a range of \(\pi(t)\) spanning eight orders of magnitude, from 21.77 down to \(2\times10^{-7}\). Two independent approximations to the same quantity, forty years apart, agree everywhere they can be compared.

4 Comments

4.1 The grid, and a correction to an earlier treatment

The bucket sizes above are not the obvious ones, and the \(\lambda = 100\) table is the reason. An earlier treatment of these tables in the technical guides used bs = 1/16 for \(\lambda = 100\), which is ample for the body and not for the tail, as Table 6 shows.

sweep = []
for bs in (1 / 16, 1 / 64, 1 / 256):
    got = stop_loss(100, bs)
    for t, exact in PAPER[100].items():
        sweep.append({'t': t, 'bs': f'1/{int(1/bs)}',
                      'rel err': abs(got[t] / exact - 1)})
piv = pd.DataFrame(sweep).pivot(index='t', columns='bs', values='rel err')
piv = piv[[f'1/{d}' for d in (16, 64, 256)]]
GT(piv, formatters={c: '{x:.2e}' for c in piv.columns})
Table 6: Relative error on Table 3 at three bucket sizes, showing where the coarse grid fails.
t1/161/641/256
1801.61e-041.45e-041.44e-04
2001.27e-045.76e-055.32e-05
2202.61e-046.48e-055.25e-05
2404.23e-044.41e-062.17e-05
2606.95e-044.91e-059.56e-05
2801.31e-031.28e-045.44e-05
3001.60e-031.15e-042.23e-04

In Table 6, at bs = 1/16 the last three rows of Table 3 miss the printed precision, by up to \(1.6\times10^{-3}\) at \(t = 300\). Refining to bs = 1/64 brings every row inside \(2\times10^{-4}\) and refining again changes little, so the residual was discretization on this side and not an inaccuracy in Gerber’s column. That is worth stating the right way round: the first version of this comparison looked like a small failure of the paper’s far tail digits, and it was not.

The general point is the one Mack and Fackler (2003) makes from a different direction. A grid sized for the mean is not automatically a grid sized for a tail eight orders of magnitude down, and the quantity that reveals it is the one being read, not the mean or the total mass, both of which are already exact to twelve figures at bs = 1/16.

4.2 What became of the three methods

Gerber’s three discretizations are still the live choices, and aggregate exposes the same distinction as discretization_calc, which computes the discrete probabilities from the survival function or the distribution function and so is accurate on the right or the left tail. The library’s default is the moment preserving idea rather than either bound, which is Gerber’s method (c) and his own recommendation.

What has changed is that the bound is no longer the thing you reach for. Gerber needed bounds because his exact answer cost as much as the bracket did, so bracketing was the cheap way to know he was close. Here the fine grid is the cheap option: the whole of Table 3 at bs = 1/256 costs a fraction of a second, so the honest error control is refinement and comparison, and this page uses it above rather than a bound.

4.3 Why a uniform severity is a good test and a bad model

Uniform on \((1, 3)\) is not a claim size distribution anybody would fit. It is a deliberately awkward test case, and the awkward part is the two hard edges. A severity with compact support and a discontinuous density at both ends is the worst case for any discretization scheme, because the error does not smooth out across a cell boundary the way it does for a density that decays. That the reproduction holds to four figures on a severity chosen to be difficult, rather than on a lognormal, says more than the same result on a friendly curve would.

It also has an exact aggregate at small \(\lambda\), which is how the \(t = 0\) row can be checked to twelve figures: \(\pi(0) = \mathsf E[S] = 2\lambda\) with no discretization involved at all.